shakebug-react-native 2.4.8 → 2.4.10
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 +0 -12
- package/VideoPlayer/PlayVideo.tsx +4 -46
- package/VideoPlayer/index.js +6 -43
- package/android/src/main/java/com/shakebug/BaseUtils.java +7 -19
- package/android/src/main/java/com/shakebug/DeviceUtil.java +34 -21
- package/android/src/main/java/com/shakebug/DrawingView.java +8 -3
- package/android/src/main/java/com/shakebug/RecordScreenModule.java +1 -2
- package/android/src/main/java/com/shakebug/ShakebugPackage.java +3 -3
- package/android/src/main/java/com/shakebug/UnitConverter.java +8 -8
- package/android/src/main/java/com/shakebug/VideoPlayerModule.java +81 -0
- package/android/src/main/java/com/shakebug/VideoTrimModule.java +387 -0
- package/android/src/main/java/com/shakebug/VideoTrimUtil.java +133 -0
- package/android/src/main/java/com/shakebug/ViewShot.java +5 -2
- package/android/src/main/java/com/shakebug/adapters/VideoTrimmerAdapter.java +53 -0
- package/android/src/main/java/com/shakebug/callback/SingleCallback.java +0 -6
- package/android/src/main/java/com/shakebug/thread/BackgroundExecutor.java +1 -93
- package/android/src/main/java/com/shakebug/thread/UiThreadExecutor.java +0 -37
- package/android/src/main/java/com/shakebug/utils/StorageUtil.java +135 -0
- package/android/src/main/java/com/shakebug/utils/VideoTrimmerUtil.java +70 -0
- package/android/src/main/java/com/shakebug/widgets/RangeSeekBarView.java +534 -0
- package/android/src/main/java/com/shakebug/widgets/SpacesItemDecoration2.java +1 -2
- package/android/src/main/java/com/shakebug/widgets/VideoPlayerView.java +317 -0
- package/android/src/main/java/com/shakebug/widgets/VideoTrimmerView.java +520 -0
- package/android/src/main/res/drawable/ic_volume_off.xml +10 -0
- package/android/src/main/res/drawable/ic_volume_on.xml +10 -0
- package/android/src/main/res/layout/{video_player_view.xml → shakebug_video_player_view.xml} +29 -9
- package/ios/TrimVideo/ShakebugSDKVideoTrim.swift +389 -0
- package/ios/TrimVideo/VideoTrim-Bridging-Header.h +10 -0
- package/ios/TrimVideo/VideoTrim.m +23 -0
- package/ios/TrimVideo/VideoTrimmer.swift +810 -0
- package/ios/TrimVideo/VideoTrimmerThumb.swift +119 -0
- package/ios/TrimVideo/VideoTrimmerViewController.swift +322 -0
- package/ios/VideoPlayer/VideoPlayerModule.m +23 -7
- package/package.json +1 -1
- package/src/APIs/AddIssue.js +11 -1
- package/src/Componanats/CustomAlert.js +2 -6
- package/src/Componanats/ErrorHandler.js +10 -1
- package/src/Componanats/LoadingModal.js +59 -0
- package/src/Componanats/ShakeFunction.js +2 -10
- package/src/Screens/AddDetailsScreen.js +29 -33
- package/src/Screens/FirstScreen.js +15 -19
- package/src/Screens/Shakebug.js +18 -26
- package/src/Screens/TrimVideoScreen.js +37 -22
- package/src/index.tsx +4 -4
- package/trimVideo/src/index.tsx +12 -48
- /package/android/src/main/res/layout/{video_thumb_item_layout.xml → shakebug_video_thumb_item_layout.xml} +0 -0
- /package/android/src/main/res/layout/{video_trimmer_view.xml → shakebug_video_trimmer_view.xml} +0 -0
|
@@ -1,18 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Copyright (C) 2010-2016 eBusiness Information, Excilys Group
|
|
3
|
-
* <p>
|
|
4
|
-
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
|
5
|
-
* use this file except in compliance with the License. You may obtain a copy of
|
|
6
|
-
* the License at
|
|
7
|
-
* <p>
|
|
8
|
-
* http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
-
* <p>
|
|
10
|
-
* Unless required by applicable law or agreed To in writing, software
|
|
11
|
-
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
12
|
-
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
13
|
-
* License for the specific language governing permissions and limitations under
|
|
14
|
-
* the License.
|
|
15
|
-
*/
|
|
16
1
|
package com.shakebug.thread;
|
|
17
2
|
|
|
18
3
|
import android.util.Log;
|
|
@@ -39,24 +24,9 @@ public final class BackgroundExecutor {
|
|
|
39
24
|
private BackgroundExecutor() {
|
|
40
25
|
}
|
|
41
26
|
|
|
42
|
-
/**
|
|
43
|
-
* Execute a runnable after the given delay.
|
|
44
|
-
*
|
|
45
|
-
* @param runnable the task to execute
|
|
46
|
-
* @param delay the time from now to delay execution, in milliseconds
|
|
47
|
-
* <p>
|
|
48
|
-
* if <code>delay</code> is strictly positive and the current
|
|
49
|
-
* executor does not support scheduling (if
|
|
50
|
-
* Executor has been called with such an
|
|
51
|
-
* executor)
|
|
52
|
-
* @return Future associated to the running task
|
|
53
|
-
* @throws IllegalArgumentException if the current executor set by Executor
|
|
54
|
-
* does not support scheduling
|
|
55
|
-
*/
|
|
56
27
|
private static Future<?> directExecute(Runnable runnable, long delay) {
|
|
57
28
|
Future<?> future = null;
|
|
58
29
|
if (delay > 0) {
|
|
59
|
-
/* no serial, but a delay: schedule the task */
|
|
60
30
|
if (!(executor instanceof ScheduledExecutorService)) {
|
|
61
31
|
throw new IllegalArgumentException("The executor set does not support scheduling");
|
|
62
32
|
}
|
|
@@ -67,24 +37,12 @@ public final class BackgroundExecutor {
|
|
|
67
37
|
ExecutorService executorService = (ExecutorService) executor;
|
|
68
38
|
future = executorService.submit(runnable);
|
|
69
39
|
} else {
|
|
70
|
-
/* non-cancellable task */
|
|
71
40
|
executor.execute(runnable);
|
|
72
41
|
}
|
|
73
42
|
}
|
|
74
43
|
return future;
|
|
75
44
|
}
|
|
76
45
|
|
|
77
|
-
/**
|
|
78
|
-
* Execute a task after (at least) its delay <strong>and</strong> after all
|
|
79
|
-
* tasks added with the same non-null <code>serial</code> (if any) have
|
|
80
|
-
* completed execution.
|
|
81
|
-
*
|
|
82
|
-
* @param task the task to execute
|
|
83
|
-
* @throws IllegalArgumentException if <code>task.delay</code> is strictly positive and the
|
|
84
|
-
* current executor does not support scheduling (if
|
|
85
|
-
* Executor has been called with such an
|
|
86
|
-
* executor)
|
|
87
|
-
*/
|
|
88
46
|
public static synchronized void execute(Task task) {
|
|
89
47
|
Future<?> future = null;
|
|
90
48
|
if (task.serial == null || !hasSerialRunning(task.serial)) {
|
|
@@ -92,20 +50,11 @@ public final class BackgroundExecutor {
|
|
|
92
50
|
future = directExecute(task, task.remainingDelay);
|
|
93
51
|
}
|
|
94
52
|
if ((task.id != null || task.serial != null) && !task.managed.get()) {
|
|
95
|
-
/* keep task */
|
|
96
53
|
task.future = future;
|
|
97
54
|
TASKS.add(task);
|
|
98
55
|
}
|
|
99
56
|
}
|
|
100
57
|
|
|
101
|
-
/**
|
|
102
|
-
* Indicates whether a task with the specified <code>serial</code> has been
|
|
103
|
-
* submitted to the executor.
|
|
104
|
-
*
|
|
105
|
-
* @param serial the serial queue
|
|
106
|
-
* @return <code>true</code> if such a task has been submitted,
|
|
107
|
-
* <code>false</code> otherwise
|
|
108
|
-
*/
|
|
109
58
|
private static boolean hasSerialRunning(String serial) {
|
|
110
59
|
for (Task task : TASKS) {
|
|
111
60
|
if (task.executionAsked && serial.equals(task.serial)) {
|
|
@@ -115,13 +64,6 @@ public final class BackgroundExecutor {
|
|
|
115
64
|
return false;
|
|
116
65
|
}
|
|
117
66
|
|
|
118
|
-
/**
|
|
119
|
-
* Retrieve and remove the first task having the specified
|
|
120
|
-
* <code>serial</code> (if any).
|
|
121
|
-
*
|
|
122
|
-
* @param serial the serial queue
|
|
123
|
-
* @return task if found, <code>null</code> otherwise
|
|
124
|
-
*/
|
|
125
67
|
private static Task take(String serial) {
|
|
126
68
|
int len = TASKS.size();
|
|
127
69
|
for (int i = 0; i < len; i++) {
|
|
@@ -132,14 +74,6 @@ public final class BackgroundExecutor {
|
|
|
132
74
|
return null;
|
|
133
75
|
}
|
|
134
76
|
|
|
135
|
-
/**
|
|
136
|
-
* Cancel all tasks having the specified <code>id</code>.
|
|
137
|
-
*
|
|
138
|
-
* @param id the cancellation identifier
|
|
139
|
-
* @param mayInterruptIfRunning <code>true</code> if the thread executing this task should be
|
|
140
|
-
* interrupted; otherwise, in-progress tasks are allowed to
|
|
141
|
-
* complete
|
|
142
|
-
*/
|
|
143
77
|
public static synchronized void cancelAll(String id, boolean mayInterruptIfRunning) {
|
|
144
78
|
for (int i = TASKS.size() - 1; i >= 0; i--) {
|
|
145
79
|
Task task = TASKS.get(i);
|
|
@@ -147,17 +81,11 @@ public final class BackgroundExecutor {
|
|
|
147
81
|
if (task.future != null) {
|
|
148
82
|
task.future.cancel(mayInterruptIfRunning);
|
|
149
83
|
if (!task.managed.getAndSet(true)) {
|
|
150
|
-
/*
|
|
151
|
-
* the task has been submitted to the executor, but its
|
|
152
|
-
* execution has not started yet, so that its run()
|
|
153
|
-
* method will never call postExecute()
|
|
154
|
-
*/
|
|
155
84
|
task.postExecute();
|
|
156
85
|
}
|
|
157
86
|
} else if (task.executionAsked) {
|
|
158
87
|
Log.w(TAG, "A task with id " + task.id + " cannot be cancelled (the executor set does not support it)");
|
|
159
88
|
} else {
|
|
160
|
-
/* this task has not been submitted to the executor */
|
|
161
89
|
TASKS.remove(i);
|
|
162
90
|
}
|
|
163
91
|
}
|
|
@@ -168,23 +96,10 @@ public final class BackgroundExecutor {
|
|
|
168
96
|
|
|
169
97
|
private String id;
|
|
170
98
|
private long remainingDelay;
|
|
171
|
-
private long targetTimeMillis;
|
|
99
|
+
private long targetTimeMillis;
|
|
172
100
|
private String serial;
|
|
173
101
|
private boolean executionAsked;
|
|
174
102
|
private Future<?> future;
|
|
175
|
-
|
|
176
|
-
/*
|
|
177
|
-
* A task can be cancelled after it has been submitted to the executor
|
|
178
|
-
* but before its run() method is called. In that case, run() will never
|
|
179
|
-
* be called, hence neither will postExecute(): the tasks with the same
|
|
180
|
-
* serial identifier (if any) will never be submitted.
|
|
181
|
-
*
|
|
182
|
-
* Therefore, cancelAll() *must* call postExecute() if run() is not
|
|
183
|
-
* started.
|
|
184
|
-
*
|
|
185
|
-
* This flag guarantees that either cancelAll() or run() manages this
|
|
186
|
-
* task post execution, but not both.
|
|
187
|
-
*/
|
|
188
103
|
private AtomicBoolean managed = new AtomicBoolean();
|
|
189
104
|
|
|
190
105
|
public Task(String id, long delay, String serial) {
|
|
@@ -203,7 +118,6 @@ public final class BackgroundExecutor {
|
|
|
203
118
|
@Override
|
|
204
119
|
public void run() {
|
|
205
120
|
if (managed.getAndSet(true)) {
|
|
206
|
-
/* cancelled and postExecute() already called */
|
|
207
121
|
return;
|
|
208
122
|
}
|
|
209
123
|
|
|
@@ -211,7 +125,6 @@ public final class BackgroundExecutor {
|
|
|
211
125
|
CURRENT_SERIAL.set(serial);
|
|
212
126
|
execute();
|
|
213
127
|
} finally {
|
|
214
|
-
/* handle next tasks */
|
|
215
128
|
postExecute();
|
|
216
129
|
}
|
|
217
130
|
}
|
|
@@ -220,22 +133,18 @@ public final class BackgroundExecutor {
|
|
|
220
133
|
|
|
221
134
|
private void postExecute() {
|
|
222
135
|
if (id == null && serial == null) {
|
|
223
|
-
/* nothing to do */
|
|
224
136
|
return;
|
|
225
137
|
}
|
|
226
138
|
CURRENT_SERIAL.set(null);
|
|
227
139
|
synchronized (BackgroundExecutor.class) {
|
|
228
|
-
/* execution complete */
|
|
229
140
|
TASKS.remove(this);
|
|
230
141
|
|
|
231
142
|
if (serial != null) {
|
|
232
143
|
Task next = take(serial);
|
|
233
144
|
if (next != null) {
|
|
234
145
|
if (next.remainingDelay != 0) {
|
|
235
|
-
/* the delay may not have elapsed yet */
|
|
236
146
|
next.remainingDelay = Math.max(0L, targetTimeMillis - System.currentTimeMillis());
|
|
237
147
|
}
|
|
238
|
-
/* a task having the same serial was queued, execute it */
|
|
239
148
|
BackgroundExecutor.execute(next);
|
|
240
149
|
}
|
|
241
150
|
}
|
|
@@ -243,4 +152,3 @@ public final class BackgroundExecutor {
|
|
|
243
152
|
}
|
|
244
153
|
}
|
|
245
154
|
}
|
|
246
|
-
|
|
@@ -1,18 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Copyright (C) 2010-2016 eBusiness Information, Excilys Group
|
|
3
|
-
* <p>
|
|
4
|
-
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
|
5
|
-
* use this file except in compliance with the License. You may obtain a copy of
|
|
6
|
-
* the License at
|
|
7
|
-
* <p>
|
|
8
|
-
* http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
-
* <p>
|
|
10
|
-
* Unless required by applicable law or agreed To in writing, software
|
|
11
|
-
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
12
|
-
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
13
|
-
* License for the specific language governing permissions and limitations under
|
|
14
|
-
* the License.
|
|
15
|
-
*/
|
|
16
1
|
package com.shakebug.thread;
|
|
17
2
|
|
|
18
3
|
import android.os.Handler;
|
|
@@ -23,10 +8,6 @@ import android.os.SystemClock;
|
|
|
23
8
|
import java.util.HashMap;
|
|
24
9
|
import java.util.Map;
|
|
25
10
|
|
|
26
|
-
/**
|
|
27
|
-
* This class provide operations for
|
|
28
|
-
* UiThread tasks.
|
|
29
|
-
*/
|
|
30
11
|
public final class UiThreadExecutor {
|
|
31
12
|
|
|
32
13
|
private static final Handler HANDLER = new Handler(Looper.getMainLooper()) {
|
|
@@ -45,17 +26,8 @@ public final class UiThreadExecutor {
|
|
|
45
26
|
private static final Map<String, Token> TOKENS = new HashMap<>();
|
|
46
27
|
|
|
47
28
|
private UiThreadExecutor() {
|
|
48
|
-
// should not be instantiated
|
|
49
29
|
}
|
|
50
30
|
|
|
51
|
-
/**
|
|
52
|
-
* Store a new task in the map for providing cancellation. This method is
|
|
53
|
-
* used by AndroidAnnotations and not intended to be called by clients.
|
|
54
|
-
*
|
|
55
|
-
* @param id the identifier of the task
|
|
56
|
-
* @param task the task itself
|
|
57
|
-
* @param delay the delay or zero to run immediately
|
|
58
|
-
*/
|
|
59
31
|
public static void runTask(String id, Runnable task, long delay) {
|
|
60
32
|
if ("".equals(id)) {
|
|
61
33
|
HANDLER.postDelayed(task, delay);
|
|
@@ -83,26 +55,18 @@ public final class UiThreadExecutor {
|
|
|
83
55
|
String id = token.id;
|
|
84
56
|
Token old = TOKENS.remove(id);
|
|
85
57
|
if (old != token) {
|
|
86
|
-
// a runnable finished after cancelling, we just removed a
|
|
87
|
-
// wrong token, lets put it back
|
|
88
58
|
TOKENS.put(id, old);
|
|
89
59
|
}
|
|
90
60
|
}
|
|
91
61
|
}
|
|
92
62
|
}
|
|
93
63
|
|
|
94
|
-
/**
|
|
95
|
-
* Cancel all tasks having the specified <code>id</code>.
|
|
96
|
-
*
|
|
97
|
-
* @param id the cancellation identifier
|
|
98
|
-
*/
|
|
99
64
|
public static void cancelAll(String id) {
|
|
100
65
|
Token token;
|
|
101
66
|
synchronized (TOKENS) {
|
|
102
67
|
token = TOKENS.remove(id);
|
|
103
68
|
}
|
|
104
69
|
if (token == null) {
|
|
105
|
-
// nothing to cancel
|
|
106
70
|
return;
|
|
107
71
|
}
|
|
108
72
|
HANDLER.removeCallbacksAndMessages(token);
|
|
@@ -116,5 +80,4 @@ public final class UiThreadExecutor {
|
|
|
116
80
|
this.id = id;
|
|
117
81
|
}
|
|
118
82
|
}
|
|
119
|
-
|
|
120
83
|
}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
package com.shakebug.utils;
|
|
2
|
+
|
|
3
|
+
import android.content.ContentValues;
|
|
4
|
+
import android.content.Context;
|
|
5
|
+
import android.media.MediaScannerConnection;
|
|
6
|
+
import android.net.Uri;
|
|
7
|
+
import android.os.Build;
|
|
8
|
+
import android.os.Environment;
|
|
9
|
+
import android.provider.MediaStore;
|
|
10
|
+
import android.text.TextUtils;
|
|
11
|
+
import com.facebook.react.bridge.ReactApplicationContext;
|
|
12
|
+
import java.io.File;
|
|
13
|
+
import java.io.FileInputStream;
|
|
14
|
+
import java.io.FileOutputStream;
|
|
15
|
+
import java.io.IOException;
|
|
16
|
+
import java.io.InputStream;
|
|
17
|
+
import java.io.OutputStream;
|
|
18
|
+
import java.util.ArrayList;
|
|
19
|
+
import java.util.List;
|
|
20
|
+
|
|
21
|
+
public class StorageUtil {
|
|
22
|
+
public static String getOutputPath(Context context) {
|
|
23
|
+
long timestamp = System.currentTimeMillis() / 1000;
|
|
24
|
+
File file = new File(context.getFilesDir(), VideoTrimmerUtil.FILE_PREFIX + "_" + timestamp + ".mp4");
|
|
25
|
+
return file.getAbsolutePath();
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
public static String[] listFiles(Context context) {
|
|
29
|
+
File filesDir = context.getFilesDir();
|
|
30
|
+
File[] files = filesDir.listFiles((dir, name) -> name.startsWith(VideoTrimmerUtil.FILE_PREFIX));
|
|
31
|
+
|
|
32
|
+
List<String> fileUrls = new ArrayList<>();
|
|
33
|
+
if (files != null) {
|
|
34
|
+
for (File file : files) {
|
|
35
|
+
fileUrls.add(file.getAbsolutePath());
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return fileUrls.toArray(new String[0]);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
public static boolean deleteFile(String path) {
|
|
43
|
+
if (TextUtils.isEmpty(path)) return true;
|
|
44
|
+
return deleteFile(new File(path));
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
public static boolean deleteFile(File file) {
|
|
48
|
+
if (file == null || !file.exists()) return true;
|
|
49
|
+
|
|
50
|
+
if (file.isFile()) {
|
|
51
|
+
return file.delete();
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (!file.isDirectory()) {
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
for (File f : file.listFiles()) {
|
|
59
|
+
if (f.isFile()) {
|
|
60
|
+
f.delete();
|
|
61
|
+
} else if (f.isDirectory()) {
|
|
62
|
+
deleteFile(f);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return file.delete();
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
public static void saveVideoToGallery(ReactApplicationContext context, String videoFilePath) throws IOException {
|
|
69
|
+
File videoFile = new File(videoFilePath);
|
|
70
|
+
|
|
71
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
|
72
|
+
saveVideoUsingMediaStore(context, videoFile);
|
|
73
|
+
} else {
|
|
74
|
+
try {
|
|
75
|
+
saveVideoUsingTraditionalStorage(context, videoFile);
|
|
76
|
+
} catch (IOException e) {
|
|
77
|
+
throw new RuntimeException(e);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
private static void saveVideoUsingMediaStore(Context context, File videoFile) {
|
|
83
|
+
ContentValues values = new ContentValues();
|
|
84
|
+
values.put(MediaStore.Video.Media.TITLE, "My Video Title");
|
|
85
|
+
values.put(MediaStore.Video.Media.MIME_TYPE, "video/mp4");
|
|
86
|
+
values.put(MediaStore.Video.Media.RELATIVE_PATH, Environment.DIRECTORY_DCIM);
|
|
87
|
+
Uri uri = context.getContentResolver().insert(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, values);
|
|
88
|
+
|
|
89
|
+
if (uri != null) {
|
|
90
|
+
try {
|
|
91
|
+
OutputStream outputStream = context.getContentResolver().openOutputStream(uri);
|
|
92
|
+
copyFile(videoFile, outputStream);
|
|
93
|
+
MediaScannerConnection.scanFile(context, new String[]{uri.toString()}, new String[]{"video/*"}, null);
|
|
94
|
+
} catch (IOException e) {
|
|
95
|
+
e.printStackTrace();
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
private static void saveVideoUsingTraditionalStorage(Context context, File videoFile) throws IOException {
|
|
101
|
+
File galleryDirectory = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM);
|
|
102
|
+
File destinationFile = new File(galleryDirectory, videoFile.getName());
|
|
103
|
+
copyFile(videoFile, destinationFile);
|
|
104
|
+
MediaScannerConnection.scanFile(context, new String[]{destinationFile.getAbsolutePath()}, new String[]{"video/*"}, null);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
private static void copyFile(File sourceFile, OutputStream outputStream) throws IOException {
|
|
108
|
+
InputStream inputStream = new FileInputStream(sourceFile);
|
|
109
|
+
copyFile(inputStream, outputStream);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
private static void copyFile(File sourceFile, File destFile) throws IOException {
|
|
113
|
+
InputStream inputStream = new FileInputStream(sourceFile);
|
|
114
|
+
OutputStream outputStream = new FileOutputStream(destFile);
|
|
115
|
+
copyFile(inputStream, outputStream);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
private static void copyFile(InputStream inputStream, OutputStream outputStream) throws IOException {
|
|
119
|
+
try {
|
|
120
|
+
byte[] buffer = new byte[1024];
|
|
121
|
+
int length;
|
|
122
|
+
while ((length = inputStream.read(buffer)) > 0) {
|
|
123
|
+
outputStream.write(buffer, 0, length);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
} finally {
|
|
127
|
+
if (inputStream != null) {
|
|
128
|
+
inputStream.close();
|
|
129
|
+
}
|
|
130
|
+
if (outputStream != null) {
|
|
131
|
+
outputStream.close();
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
package com.shakebug.utils;
|
|
2
|
+
|
|
3
|
+
import android.content.Context;
|
|
4
|
+
import android.graphics.Bitmap;
|
|
5
|
+
import android.media.MediaMetadataRetriever;
|
|
6
|
+
import android.net.Uri;
|
|
7
|
+
import com.shakebug.DeviceUtil;
|
|
8
|
+
import com.shakebug.UnitConverter;
|
|
9
|
+
import com.shakebug.VideoTrimUtil;
|
|
10
|
+
import com.shakebug.callback.SingleCallback;
|
|
11
|
+
import com.shakebug.interfaces.VideoTrimListener;
|
|
12
|
+
import com.shakebug.thread.BackgroundExecutor;
|
|
13
|
+
|
|
14
|
+
public class VideoTrimmerUtil {
|
|
15
|
+
|
|
16
|
+
private static final String TAG = VideoTrimmerUtil.class.getSimpleName();
|
|
17
|
+
public static final String FILE_PREFIX = "trimmedVideo";
|
|
18
|
+
public static final long MIN_SHOOT_DURATION = 1000L;// min 1 second for trimming
|
|
19
|
+
public static long maxShootDuration = 10 * 1000L;
|
|
20
|
+
public static int MAX_COUNT_RANGE = 10; // how many images in the highlight range of seek bar
|
|
21
|
+
public static int SCREEN_WIDTH_FULL = DeviceUtil.getDeviceWidth();
|
|
22
|
+
public static final int RECYCLER_VIEW_PADDING = UnitConverter.dpToPx(35);
|
|
23
|
+
public static int VIDEO_FRAMES_WIDTH = SCREEN_WIDTH_FULL - RECYCLER_VIEW_PADDING * 2;
|
|
24
|
+
public static int mThumbWidth = 0; // make it automatic
|
|
25
|
+
public static final int THUMB_HEIGHT = UnitConverter.dpToPx(50); // x2 for better resolution
|
|
26
|
+
private static final int THUMB_RESOLUTION_RES = 2; // double thumb resolution for better quality
|
|
27
|
+
|
|
28
|
+
public static void trim(String inputFile, String outputFile, int videoDuration, long startMs, long endMs, final VideoTrimListener callback) {
|
|
29
|
+
callback.onStartTrim();
|
|
30
|
+
BackgroundExecutor.execute(new BackgroundExecutor.Task("", 0L, "") {
|
|
31
|
+
@Override
|
|
32
|
+
public void execute() {
|
|
33
|
+
try {
|
|
34
|
+
VideoTrimUtil.trimVideo(inputFile, outputFile, startMs, endMs);
|
|
35
|
+
callback.onTrimmingProgress(100);
|
|
36
|
+
callback.onFinishTrim(outputFile);
|
|
37
|
+
} catch (Exception e) {
|
|
38
|
+
callback.onError(e.getMessage() != null ? e.getMessage() : "Video trim failed");
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
public static void shootVideoThumbInBackground(final Context context, final Uri videoUri, final int totalThumbsCount, final long startPosition,
|
|
45
|
+
final long endPosition, final SingleCallback<Bitmap, Integer> callback) {
|
|
46
|
+
BackgroundExecutor.execute(new BackgroundExecutor.Task("", 0L, "") {
|
|
47
|
+
@Override public void execute() {
|
|
48
|
+
try {
|
|
49
|
+
MediaMetadataRetriever mediaMetadataRetriever = new MediaMetadataRetriever();
|
|
50
|
+
mediaMetadataRetriever.setDataSource(context, videoUri);
|
|
51
|
+
long interval = (endPosition - startPosition) / Math.max(1, (totalThumbsCount - 1));
|
|
52
|
+
for (long i = 0; i < totalThumbsCount; ++i) {
|
|
53
|
+
long frameTime = startPosition + interval * i;
|
|
54
|
+
Bitmap bitmap = mediaMetadataRetriever.getFrameAtTime(frameTime * 1000, MediaMetadataRetriever.OPTION_CLOSEST_SYNC);
|
|
55
|
+
if(bitmap == null) continue;
|
|
56
|
+
try {
|
|
57
|
+
bitmap = Bitmap.createScaledBitmap(bitmap, mThumbWidth * THUMB_RESOLUTION_RES, THUMB_HEIGHT * THUMB_RESOLUTION_RES, false);
|
|
58
|
+
} catch (final Throwable t) {
|
|
59
|
+
t.printStackTrace();
|
|
60
|
+
}
|
|
61
|
+
callback.onSingleCallback(bitmap, (int) interval);
|
|
62
|
+
}
|
|
63
|
+
mediaMetadataRetriever.release();
|
|
64
|
+
} catch (final Throwable e) {
|
|
65
|
+
Thread.getDefaultUncaughtExceptionHandler().uncaughtException(Thread.currentThread(), e);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
}
|