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.
Files changed (47) hide show
  1. package/README.md +0 -12
  2. package/VideoPlayer/PlayVideo.tsx +4 -46
  3. package/VideoPlayer/index.js +6 -43
  4. package/android/src/main/java/com/shakebug/BaseUtils.java +7 -19
  5. package/android/src/main/java/com/shakebug/DeviceUtil.java +34 -21
  6. package/android/src/main/java/com/shakebug/DrawingView.java +8 -3
  7. package/android/src/main/java/com/shakebug/RecordScreenModule.java +1 -2
  8. package/android/src/main/java/com/shakebug/ShakebugPackage.java +3 -3
  9. package/android/src/main/java/com/shakebug/UnitConverter.java +8 -8
  10. package/android/src/main/java/com/shakebug/VideoPlayerModule.java +81 -0
  11. package/android/src/main/java/com/shakebug/VideoTrimModule.java +387 -0
  12. package/android/src/main/java/com/shakebug/VideoTrimUtil.java +133 -0
  13. package/android/src/main/java/com/shakebug/ViewShot.java +5 -2
  14. package/android/src/main/java/com/shakebug/adapters/VideoTrimmerAdapter.java +53 -0
  15. package/android/src/main/java/com/shakebug/callback/SingleCallback.java +0 -6
  16. package/android/src/main/java/com/shakebug/thread/BackgroundExecutor.java +1 -93
  17. package/android/src/main/java/com/shakebug/thread/UiThreadExecutor.java +0 -37
  18. package/android/src/main/java/com/shakebug/utils/StorageUtil.java +135 -0
  19. package/android/src/main/java/com/shakebug/utils/VideoTrimmerUtil.java +70 -0
  20. package/android/src/main/java/com/shakebug/widgets/RangeSeekBarView.java +534 -0
  21. package/android/src/main/java/com/shakebug/widgets/SpacesItemDecoration2.java +1 -2
  22. package/android/src/main/java/com/shakebug/widgets/VideoPlayerView.java +317 -0
  23. package/android/src/main/java/com/shakebug/widgets/VideoTrimmerView.java +520 -0
  24. package/android/src/main/res/drawable/ic_volume_off.xml +10 -0
  25. package/android/src/main/res/drawable/ic_volume_on.xml +10 -0
  26. package/android/src/main/res/layout/{video_player_view.xml → shakebug_video_player_view.xml} +29 -9
  27. package/ios/TrimVideo/ShakebugSDKVideoTrim.swift +389 -0
  28. package/ios/TrimVideo/VideoTrim-Bridging-Header.h +10 -0
  29. package/ios/TrimVideo/VideoTrim.m +23 -0
  30. package/ios/TrimVideo/VideoTrimmer.swift +810 -0
  31. package/ios/TrimVideo/VideoTrimmerThumb.swift +119 -0
  32. package/ios/TrimVideo/VideoTrimmerViewController.swift +322 -0
  33. package/ios/VideoPlayer/VideoPlayerModule.m +23 -7
  34. package/package.json +1 -1
  35. package/src/APIs/AddIssue.js +11 -1
  36. package/src/Componanats/CustomAlert.js +2 -6
  37. package/src/Componanats/ErrorHandler.js +10 -1
  38. package/src/Componanats/LoadingModal.js +59 -0
  39. package/src/Componanats/ShakeFunction.js +2 -10
  40. package/src/Screens/AddDetailsScreen.js +29 -33
  41. package/src/Screens/FirstScreen.js +15 -19
  42. package/src/Screens/Shakebug.js +18 -26
  43. package/src/Screens/TrimVideoScreen.js +37 -22
  44. package/src/index.tsx +4 -4
  45. package/trimVideo/src/index.tsx +12 -48
  46. /package/android/src/main/res/layout/{video_thumb_item_layout.xml → shakebug_video_thumb_item_layout.xml} +0 -0
  47. /package/android/src/main/res/layout/{video_trimmer_view.xml → shakebug_video_trimmer_view.xml} +0 -0
@@ -0,0 +1,387 @@
1
+ package com.shakebug;
2
+
3
+ import static com.facebook.react.bridge.UiThreadUtil.runOnUiThread;
4
+
5
+ import android.app.Activity;
6
+ import android.content.res.ColorStateList;
7
+ import android.graphics.Color;
8
+ import android.media.MediaMetadataRetriever;
9
+ import android.net.Uri;
10
+ import android.os.Build;
11
+ import android.view.Gravity;
12
+ import android.view.ViewGroup;
13
+ import android.widget.LinearLayout;
14
+ import android.widget.ProgressBar;
15
+ import android.widget.TextView;
16
+
17
+ import androidx.annotation.NonNull;
18
+ import androidx.annotation.Nullable;
19
+ import androidx.appcompat.app.AlertDialog;
20
+
21
+ import com.facebook.react.bridge.Arguments;
22
+ import com.facebook.react.bridge.LifecycleEventListener;
23
+ import com.facebook.react.bridge.Promise;
24
+ import com.facebook.react.bridge.ReactApplicationContext;
25
+ import com.facebook.react.bridge.ReactContext;
26
+ import com.facebook.react.bridge.ReactContextBaseJavaModule;
27
+ import com.facebook.react.bridge.ReactMethod;
28
+ import com.facebook.react.bridge.ReadableMap;
29
+ import com.facebook.react.bridge.WritableMap;
30
+ import com.facebook.react.module.annotations.ReactModule;
31
+ import com.facebook.react.modules.core.DeviceEventManagerModule;
32
+ import com.shakebug.interfaces.VideoTrimListener;
33
+ import com.shakebug.utils.StorageUtil;
34
+ import com.shakebug.widgets.VideoTrimmerView;
35
+
36
+ import java.io.IOException;
37
+
38
+ @ReactModule(name = VideoTrimModule.NAME)
39
+ public class VideoTrimModule extends ReactContextBaseJavaModule implements VideoTrimListener, LifecycleEventListener {
40
+ public static final String NAME = "ShakebugSDKVideoTrim";
41
+ private static Boolean isInit = false;
42
+ private VideoTrimmerView trimmerView;
43
+ private AlertDialog alertDialog;
44
+ private AlertDialog mProgressDialog;
45
+ private ProgressBar mProgressBar;
46
+ private int listenerCount = 0;
47
+
48
+ private Promise showEditorPromise;
49
+
50
+ private boolean enableCancelDialog = true;
51
+ private String cancelDialogTitle = "Warning!";
52
+ private String cancelDialogMessage = "Are you sure want to cancel?";
53
+ private String cancelDialogCancelText = "Close";
54
+ private String cancelDialogConfirmText = "Proceed";
55
+ private boolean enableSaveDialog = true;
56
+ private String saveDialogTitle = "Confirmation!";
57
+ private String saveDialogMessage = "Are you sure want to save?";
58
+ private String saveDialogCancelText = "Close";
59
+ private String saveDialogConfirmText = "Proceed";
60
+ private String trimmingText = "Trimming video...";
61
+
62
+ public VideoTrimModule(ReactApplicationContext reactContext) {
63
+ super(reactContext);
64
+ BaseUtils.init(reactContext);
65
+ }
66
+
67
+ @Override
68
+ @NonNull
69
+ public String getName() {
70
+ return NAME;
71
+ }
72
+
73
+ @ReactMethod
74
+ public void showEditor(String videoPath, ReadableMap config, Promise promise) {
75
+ showEditorPromise = promise;
76
+ if (trimmerView != null || alertDialog != null) {
77
+ return;
78
+ }
79
+
80
+ if (!isValidVideo(videoPath)) {
81
+ WritableMap map = Arguments.createMap();
82
+ map.putString("message", "File is not a valid video");
83
+ sendEvent(getReactApplicationContext(), "onError", map);
84
+ if (promise != null) {
85
+ promise.reject("INVALID_VIDEO", "File is not a valid video");
86
+ }
87
+ return;
88
+ }
89
+
90
+ enableCancelDialog = config.hasKey("enableCancelDialog") ? config.getBoolean("enableCancelDialog") : true;
91
+ cancelDialogTitle = config.hasKey("cancelDialogTitle") ? config.getString("cancelDialogTitle") : "Warning!";
92
+ cancelDialogMessage = config.hasKey("cancelDialogMessage") ? config.getString("cancelDialogMessage") : "Are you sure want to cancel?";
93
+ cancelDialogCancelText = config.hasKey("cancelDialogCancelText") ? config.getString("cancelDialogCancelText") : "Close";
94
+ cancelDialogConfirmText = config.hasKey("cancelDialogConfirmText") ? config.getString("cancelDialogConfirmText") : "Proceed";
95
+
96
+ enableSaveDialog = config.hasKey("enableSaveDialog") ? config.getBoolean("enableSaveDialog") : true;
97
+ saveDialogTitle = config.hasKey("saveDialogTitle") ? config.getString("saveDialogTitle") : "Confirmation!";
98
+ saveDialogMessage = config.hasKey("saveDialogMessage") ? config.getString("saveDialogMessage") : "Are you sure want to save?";
99
+ saveDialogCancelText = config.hasKey("saveDialogCancelText") ? config.getString("saveDialogCancelText") : "Close";
100
+ saveDialogConfirmText = config.hasKey("saveDialogConfirmText") ? config.getString("saveDialogConfirmText") : "Proceed";
101
+ trimmingText = config.hasKey("trimmingText") ? config.getString("trimmingText") : "Trimming video...";
102
+
103
+ BaseUtils.init(getReactApplicationContext());
104
+
105
+ runOnUiThread(() -> {
106
+ try {
107
+ Activity currentAct = getReactApplicationContext().getCurrentActivity();
108
+ if (currentAct == null || currentAct.isFinishing()) {
109
+ if (showEditorPromise != null) {
110
+ showEditorPromise.reject("NO_ACTIVITY", "Current activity is not available");
111
+ showEditorPromise = null;
112
+ }
113
+ return;
114
+ }
115
+
116
+ trimmerView = new VideoTrimmerView(currentAct, config, null);
117
+ trimmerView.setOnTrimVideoListener(this);
118
+
119
+ String cleanPath = videoPath.replace("file://", "");
120
+ Uri uri = cleanPath.startsWith("/") ? Uri.fromFile(new java.io.File(cleanPath)) : Uri.parse(videoPath);
121
+ trimmerView.initVideoByURI(uri);
122
+
123
+ AlertDialog.Builder builder = new AlertDialog.Builder(currentAct, android.R.style.Theme_Black_NoTitleBar_Fullscreen);
124
+ builder.setCancelable(false);
125
+ alertDialog = builder.create();
126
+ alertDialog.setView(trimmerView);
127
+ alertDialog.show();
128
+
129
+ alertDialog.setOnDismissListener(dialog -> {
130
+ if (trimmerView != null) {
131
+ trimmerView.onDestroy();
132
+ trimmerView = null;
133
+ }
134
+ hideDialog();
135
+ sendEvent(getReactApplicationContext(), "onHide", null);
136
+ });
137
+ sendEvent(getReactApplicationContext(), "onShow", null);
138
+ } catch (Exception e) {
139
+ e.printStackTrace();
140
+ onError(e.getMessage() != null ? e.getMessage() : "Error displaying video trimmer");
141
+ }
142
+ });
143
+ }
144
+
145
+ @Override
146
+ public void onHostResume() {
147
+
148
+ }
149
+
150
+ @Override
151
+ public void onHostPause() {
152
+ if (trimmerView != null) {
153
+ trimmerView.onVideoPause();
154
+ trimmerView.setRestoreState(true);
155
+ }
156
+ }
157
+
158
+ @Override
159
+ public void onHostDestroy() {
160
+ hideDialog();
161
+ }
162
+
163
+ @Override public void onStartTrim() {
164
+ sendEvent(getReactApplicationContext(), "onStartTrimming", null);
165
+ runOnUiThread(() -> {
166
+ buildDialog();
167
+ });
168
+ }
169
+
170
+ @Override public void onTrimmingProgress(int percentage) {
171
+ if (mProgressBar != null) {
172
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
173
+ mProgressBar.setProgress(percentage, true);
174
+ } else {
175
+ mProgressBar.setProgress(percentage);
176
+ }
177
+ }
178
+ }
179
+
180
+ @Override public void onFinishTrim(String in) {
181
+ runOnUiThread(() -> {
182
+ hideDialog();
183
+ WritableMap map = Arguments.createMap();
184
+ map.putString("outputPath", in);
185
+ sendEvent(getReactApplicationContext(), "onFinishTrimming", map);
186
+ if (showEditorPromise != null) {
187
+ showEditorPromise.resolve(in);
188
+ showEditorPromise = null;
189
+ }
190
+ });
191
+ }
192
+
193
+ @Override public void onError(String errorMessage) {
194
+ runOnUiThread(() -> {
195
+ this.hideDialog();
196
+ WritableMap map = Arguments.createMap();
197
+ map.putString("message", errorMessage);
198
+ sendEvent(getReactApplicationContext(), "onError", map);
199
+ if (showEditorPromise != null) {
200
+ showEditorPromise.reject("TRIM_ERROR", errorMessage);
201
+ showEditorPromise = null;
202
+ }
203
+ });
204
+ }
205
+
206
+ @Override public void onCancel() {
207
+ if (!enableCancelDialog) {
208
+ sendEvent(getReactApplicationContext(), "onCancel", null);
209
+ if (showEditorPromise != null) {
210
+ showEditorPromise.resolve(null);
211
+ showEditorPromise = null;
212
+ }
213
+ hideDialog();
214
+ return;
215
+ }
216
+
217
+ Activity activity = getReactApplicationContext().getCurrentActivity();
218
+ if (activity == null) return;
219
+ AlertDialog.Builder builder = new AlertDialog.Builder(activity);
220
+ builder.setMessage(cancelDialogMessage);
221
+ builder.setTitle(cancelDialogTitle);
222
+ builder.setCancelable(false);
223
+ builder.setPositiveButton(cancelDialogConfirmText, (dialog, which) -> {
224
+ dialog.cancel();
225
+ sendEvent(getReactApplicationContext(), "onCancel", null);
226
+ if (showEditorPromise != null) {
227
+ showEditorPromise.resolve(null);
228
+ showEditorPromise = null;
229
+ }
230
+ hideDialog();
231
+ });
232
+ builder.setNegativeButton(cancelDialogCancelText, (dialog, which) -> {
233
+ dialog.cancel();
234
+ });
235
+ AlertDialog d = builder.create();
236
+ d.show();
237
+ }
238
+
239
+ @Override public void onSave() {
240
+ if (!enableSaveDialog) {
241
+ if (trimmerView != null) trimmerView.onSaveClicked();
242
+ return;
243
+ }
244
+
245
+ Activity activity = getReactApplicationContext().getCurrentActivity();
246
+ if (activity == null) return;
247
+ AlertDialog.Builder builder = new AlertDialog.Builder(activity);
248
+ builder.setMessage(saveDialogMessage);
249
+ builder.setTitle(saveDialogTitle);
250
+ builder.setCancelable(false);
251
+ builder.setPositiveButton(saveDialogConfirmText, (dialog, which) -> {
252
+ dialog.cancel();
253
+ if (trimmerView != null) trimmerView.onSaveClicked();
254
+ });
255
+ builder.setNegativeButton(saveDialogCancelText, (dialog, which) -> {
256
+ dialog.cancel();
257
+ });
258
+ AlertDialog d = builder.create();
259
+ d.show();
260
+ }
261
+
262
+ @ReactMethod
263
+ public void hideDialog() {
264
+ if (mProgressDialog != null) {
265
+ if (mProgressDialog.isShowing()) mProgressDialog.dismiss();
266
+ mProgressBar = null;
267
+ mProgressDialog = null;
268
+ }
269
+
270
+ if (alertDialog != null) {
271
+ if (alertDialog.isShowing()) {
272
+ alertDialog.dismiss();
273
+ }
274
+ alertDialog = null;
275
+ }
276
+ }
277
+
278
+ private void buildDialog() {
279
+ Activity activity = getReactApplicationContext().getCurrentActivity();
280
+ if (activity == null) return;
281
+ LinearLayout layout = new LinearLayout(activity);
282
+ layout.setLayoutParams(new ViewGroup.LayoutParams(
283
+ ViewGroup.LayoutParams.WRAP_CONTENT,
284
+ ViewGroup.LayoutParams.WRAP_CONTENT
285
+ ));
286
+ layout.setOrientation(LinearLayout.VERTICAL);
287
+ layout.setGravity(Gravity.CENTER_HORIZONTAL);
288
+ layout.setPadding(16, 32, 16, 32);
289
+
290
+ TextView textView = new TextView(activity);
291
+ textView.setLayoutParams(new ViewGroup.LayoutParams(
292
+ ViewGroup.LayoutParams.WRAP_CONTENT,
293
+ ViewGroup.LayoutParams.WRAP_CONTENT
294
+ ));
295
+ textView.setText(trimmingText);
296
+ textView.setTextSize(18);
297
+ layout.addView(textView);
298
+
299
+ mProgressBar = new ProgressBar(activity, null, android.R.attr.progressBarStyleHorizontal);
300
+ mProgressBar.setLayoutParams(new ViewGroup.LayoutParams(
301
+ ViewGroup.LayoutParams.MATCH_PARENT,
302
+ ViewGroup.LayoutParams.WRAP_CONTENT
303
+ ));
304
+ mProgressBar.setProgressTintList(ColorStateList.valueOf(Color.parseColor("#2196F3")));
305
+ layout.addView(mProgressBar);
306
+
307
+ AlertDialog.Builder builder = new AlertDialog.Builder(activity);
308
+ builder.setCancelable(false);
309
+ builder.setView(layout);
310
+
311
+ mProgressDialog = builder.create();
312
+ mProgressDialog.show();
313
+ }
314
+
315
+ @ReactMethod
316
+ public void addListener(String eventName) {
317
+ listenerCount += 1;
318
+ }
319
+
320
+ @ReactMethod
321
+ public void removeListeners(Integer count) {
322
+ listenerCount -= count;
323
+ }
324
+
325
+ private void sendEvent(ReactContext reactContext, String eventName, @Nullable WritableMap params) {
326
+ WritableMap map = params != null ? params : Arguments.createMap();
327
+ map.putString("name", eventName);
328
+ reactContext
329
+ .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
330
+ .emit("VideoTrim", map);
331
+ }
332
+
333
+ public boolean isValidVideo(String filePath) {
334
+ if (filePath == null || filePath.isEmpty()) return false;
335
+ MediaMetadataRetriever retriever = new MediaMetadataRetriever();
336
+ try {
337
+ retriever.setDataSource(getReactApplicationContext(), Uri.parse(filePath));
338
+ } catch (Exception e) {
339
+ return false;
340
+ }
341
+ String hasVideo = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_HAS_VIDEO);
342
+ return "yes".equals(hasVideo);
343
+ }
344
+
345
+ @ReactMethod
346
+ public void isValidVideo(String filePath, Promise promise) {
347
+ promise.resolve(isValidVideo(filePath));
348
+ }
349
+
350
+ @ReactMethod
351
+ public void saveVideo(String filePath, Promise promise) {
352
+ try {
353
+ StorageUtil.saveVideoToGallery(getReactApplicationContext(), filePath);
354
+ } catch (IOException e) {
355
+ WritableMap mapE = Arguments.createMap();
356
+ mapE.putString("message", "Fail while copying file to Gallery");
357
+ sendEvent(getReactApplicationContext(), "onError", mapE);
358
+ }
359
+ this.hideDialog();
360
+ promise.resolve(null);
361
+ }
362
+
363
+ @ReactMethod
364
+ public void listFiles(Promise promise) {
365
+ String[] files = StorageUtil.listFiles(getReactApplicationContext());
366
+ promise.resolve(Arguments.fromArray(files));
367
+ }
368
+
369
+ @ReactMethod
370
+ public void cleanFiles(Promise promise) {
371
+ String[] files = StorageUtil.listFiles(getReactApplicationContext());
372
+ int successCount = 0;
373
+ for (String file : files) {
374
+ boolean state = StorageUtil.deleteFile(file);
375
+ if (state) {
376
+ successCount++;
377
+ }
378
+ }
379
+ promise.resolve(successCount);
380
+ }
381
+
382
+ @ReactMethod
383
+ public void deleteFile(String filePath, Promise promise) {
384
+ boolean state = StorageUtil.deleteFile(filePath);
385
+ promise.resolve(state);
386
+ }
387
+ }
@@ -0,0 +1,133 @@
1
+ package com.shakebug;
2
+
3
+ import android.media.MediaCodec;
4
+ import android.media.MediaExtractor;
5
+ import android.media.MediaFormat;
6
+ import android.media.MediaMetadataRetriever;
7
+ import android.media.MediaMuxer;
8
+ import android.util.Log;
9
+
10
+ import java.io.IOException;
11
+ import java.nio.ByteBuffer;
12
+ import java.util.HashMap;
13
+
14
+ public class VideoTrimUtil {
15
+ private static final String TAG = "VideoTrimUtil";
16
+ private static final int DEFAULT_BUFFER_SIZE = 1024 * 1024;
17
+
18
+ public static long getVideoDurationMs(String sourcePath) {
19
+ MediaMetadataRetriever retriever = new MediaMetadataRetriever();
20
+ try {
21
+ retriever.setDataSource(sourcePath);
22
+ String duration = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION);
23
+ return duration != null ? Long.parseLong(duration) : 0;
24
+ } catch (Exception e) {
25
+ Log.e(TAG, "Error reading video duration", e);
26
+ return 0;
27
+ } finally {
28
+ try {
29
+ retriever.release();
30
+ } catch (Exception ignored) {}
31
+ }
32
+ }
33
+
34
+ public static void trimVideo(String srcPath, String destPath, long startMs, long endMs) throws IOException {
35
+ MediaExtractor extractor = new MediaExtractor();
36
+ MediaMuxer muxer = null;
37
+
38
+ try {
39
+ extractor.setDataSource(srcPath);
40
+ int trackCount = extractor.getTrackCount();
41
+
42
+ muxer = new MediaMuxer(destPath, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
43
+ HashMap<Integer, Integer> indexMap = new HashMap<>(trackCount);
44
+
45
+ // Preserve video orientation if available
46
+ try {
47
+ MediaMetadataRetriever retriever = new MediaMetadataRetriever();
48
+ retriever.setDataSource(srcPath);
49
+ String rotationStr = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_ROTATION);
50
+ if (rotationStr != null) {
51
+ int rotation = Integer.parseInt(rotationStr);
52
+ muxer.setOrientationHint(rotation);
53
+ }
54
+ retriever.release();
55
+ } catch (Exception ignored) {}
56
+
57
+ int bufferSize = -1;
58
+ for (int i = 0; i < trackCount; i++) {
59
+ MediaFormat format = extractor.getTrackFormat(i);
60
+ String mime = format.getString(MediaFormat.KEY_MIME);
61
+ if (mime != null && (mime.startsWith("video/") || mime.startsWith("audio/"))) {
62
+ int dstIndex = muxer.addTrack(format);
63
+ indexMap.put(i, dstIndex);
64
+ if (format.containsKey(MediaFormat.KEY_MAX_INPUT_SIZE)) {
65
+ int newSize = format.getInteger(MediaFormat.KEY_MAX_INPUT_SIZE);
66
+ bufferSize = Math.max(newSize, bufferSize);
67
+ }
68
+ }
69
+ }
70
+
71
+ if (bufferSize < 0) {
72
+ bufferSize = DEFAULT_BUFFER_SIZE;
73
+ }
74
+
75
+ muxer.start();
76
+
77
+ long startUs = startMs * 1000L;
78
+ long endUs = endMs * 1000L;
79
+
80
+ ByteBuffer dstBuf = ByteBuffer.allocate(bufferSize);
81
+ MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
82
+
83
+ // Extract each track independently
84
+ for (int srcTrackIndex : indexMap.keySet()) {
85
+ // Select only the current track
86
+ for (int i = 0; i < trackCount; i++) {
87
+ extractor.unselectTrack(i);
88
+ }
89
+ extractor.selectTrack(srcTrackIndex);
90
+ extractor.seekTo(startUs, MediaExtractor.SEEK_TO_CLOSEST_SYNC);
91
+
92
+ int dstTrackIndex = indexMap.get(srcTrackIndex);
93
+
94
+ while (true) {
95
+ bufferInfo.offset = 0;
96
+ bufferInfo.size = extractor.readSampleData(dstBuf, 0);
97
+
98
+ if (bufferInfo.size < 0) {
99
+ break;
100
+ }
101
+
102
+ long presentationTimeUs = extractor.getSampleTime();
103
+ if (presentationTimeUs > endUs) {
104
+ break;
105
+ }
106
+
107
+ if (presentationTimeUs >= startUs) {
108
+ bufferInfo.presentationTimeUs = presentationTimeUs - startUs;
109
+ bufferInfo.flags = extractor.getSampleFlags();
110
+ muxer.writeSampleData(dstTrackIndex, dstBuf, bufferInfo);
111
+ }
112
+
113
+ if (!extractor.advance()) {
114
+ break;
115
+ }
116
+ }
117
+ }
118
+
119
+ } finally {
120
+ try {
121
+ if (muxer != null) {
122
+ muxer.stop();
123
+ muxer.release();
124
+ }
125
+ } catch (Exception e) {
126
+ Log.e(TAG, "Error releasing muxer", e);
127
+ }
128
+ try {
129
+ extractor.release();
130
+ } catch (Exception ignored) {}
131
+ }
132
+ }
133
+ }
@@ -351,10 +351,13 @@ public class ViewShot implements UIBlock {
351
351
 
352
352
  // evaluate real height
353
353
  if (snapshotContentContainer) {
354
- h = 0;
354
+ int evaluatedHeight = 0;
355
355
  ScrollView scrollView = (ScrollView) view;
356
356
  for (int i = 0; i < scrollView.getChildCount(); i++) {
357
- h += scrollView.getChildAt(i).getHeight();
357
+ evaluatedHeight += scrollView.getChildAt(i).getHeight();
358
+ }
359
+ if (evaluatedHeight > 0) {
360
+ h = evaluatedHeight;
358
361
  }
359
362
  }
360
363
 
@@ -0,0 +1,53 @@
1
+ package com.shakebug.adapters;
2
+
3
+ import android.content.Context;
4
+ import android.graphics.Bitmap;
5
+ import android.view.LayoutInflater;
6
+ import android.view.View;
7
+ import android.view.ViewGroup;
8
+ import android.widget.ImageView;
9
+ import android.widget.LinearLayout;
10
+ import androidx.annotation.NonNull;
11
+ import androidx.recyclerview.widget.RecyclerView;
12
+ import com.shakebug.R;
13
+ import com.shakebug.utils.VideoTrimmerUtil;
14
+ import java.util.ArrayList;
15
+ import java.util.List;
16
+
17
+ public class VideoTrimmerAdapter extends RecyclerView.Adapter {
18
+ private final List<Bitmap> mBitmaps = new ArrayList<>();
19
+ private final LayoutInflater mInflater;
20
+
21
+ public VideoTrimmerAdapter(Context context) {
22
+ this.mInflater = LayoutInflater.from(context);
23
+ }
24
+
25
+ @NonNull @Override public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
26
+ return new TrimmerViewHolder(mInflater.inflate(R.layout.shakebug_video_thumb_item_layout, parent, false));
27
+ }
28
+
29
+ @Override public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
30
+ ((TrimmerViewHolder) holder).thumbImageView.setImageBitmap(mBitmaps.get(position));
31
+ }
32
+
33
+ @Override public int getItemCount() {
34
+ return mBitmaps.size();
35
+ }
36
+
37
+ public void addBitmaps(Bitmap bitmap) {
38
+ mBitmaps.add(bitmap);
39
+ notifyItemInserted(mBitmaps.size() - 1);
40
+ }
41
+
42
+ private static final class TrimmerViewHolder extends RecyclerView.ViewHolder {
43
+ ImageView thumbImageView;
44
+
45
+ TrimmerViewHolder(View itemView) {
46
+ super(itemView);
47
+ thumbImageView = itemView.findViewById(R.id.thumb);
48
+ LinearLayout.LayoutParams layoutParams = (LinearLayout.LayoutParams) thumbImageView.getLayoutParams();
49
+ layoutParams.width = VideoTrimmerUtil.VIDEO_FRAMES_WIDTH / VideoTrimmerUtil.MAX_COUNT_RANGE;
50
+ thumbImageView.setLayoutParams(layoutParams);
51
+ }
52
+ }
53
+ }
@@ -1,11 +1,5 @@
1
1
  package com.shakebug.callback;
2
2
 
3
- /**
4
- * Author:J.Chou
5
- * Date: 2016.07.20 19:01.
6
- * Email: who_know_me@163.com
7
- * Describe:
8
- */
9
3
  public interface SingleCallback<T,V> {
10
4
  void onSingleCallback(T t,V v);
11
5
  }