react-native-video-trim 4.1.0 → 5.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (99) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +89 -76
  3. package/VideoTrim.podspec +3 -3
  4. package/android/build.gradle +6 -53
  5. package/android/gradle.properties +1 -1
  6. package/android/src/main/AndroidManifest.xml +1 -1
  7. package/android/src/main/java/com/{margelo/nitro/videotrim/VideoTrim.kt → videotrim/VideoTrimModule.kt} +246 -232
  8. package/android/src/main/java/com/videotrim/VideoTrimPackage.kt +33 -0
  9. package/android/src/main/java/com/{margelo/nitro/videotrim → videotrim}/enums/ErrorCode.java +1 -1
  10. package/android/src/main/java/com/{margelo/nitro/videotrim → videotrim}/interfaces/IVideoTrimmerView.java +1 -1
  11. package/android/src/main/java/com/{margelo/nitro/videotrim → videotrim}/interfaces/VideoTrimListener.java +5 -4
  12. package/android/src/main/java/com/{margelo/nitro/videotrim → videotrim}/utils/MediaMetadataUtil.java +1 -1
  13. package/android/src/main/java/com/{margelo/nitro/videotrim → videotrim}/utils/StorageUtil.java +1 -1
  14. package/android/src/main/java/com/{margelo/nitro/videotrim → videotrim}/utils/VideoTrimmerUtil.java +20 -18
  15. package/android/src/main/java/com/{margelo/nitro/videotrim → videotrim}/widgets/VideoTrimmerView.java +44 -45
  16. package/ios/AssetLoader.h +19 -0
  17. package/ios/AssetLoader.mm +87 -0
  18. package/ios/ErrorCode.h +9 -0
  19. package/ios/ProgressAlertController.h +15 -0
  20. package/ios/ProgressAlertController.mm +78 -0
  21. package/ios/VideoTrim.h +31 -0
  22. package/ios/VideoTrim.mm +663 -0
  23. package/ios/VideoTrimmer.h +67 -0
  24. package/ios/VideoTrimmer.mm +863 -0
  25. package/ios/VideoTrimmerThumb.h +23 -0
  26. package/ios/VideoTrimmerThumb.mm +175 -0
  27. package/ios/VideoTrimmerViewController.h +52 -0
  28. package/ios/VideoTrimmerViewController.mm +533 -0
  29. package/lib/module/NativeVideoTrim.js +5 -0
  30. package/lib/module/NativeVideoTrim.js.map +1 -0
  31. package/lib/module/index.js +24 -24
  32. package/lib/module/index.js.map +1 -1
  33. package/lib/typescript/src/NativeVideoTrim.d.ts +107 -0
  34. package/lib/typescript/src/NativeVideoTrim.d.ts.map +1 -0
  35. package/lib/typescript/src/index.d.ts +16 -10
  36. package/lib/typescript/src/index.d.ts.map +1 -1
  37. package/package.json +15 -18
  38. package/src/NativeVideoTrim.ts +113 -0
  39. package/src/index.tsx +29 -31
  40. package/android/CMakeLists.txt +0 -24
  41. package/android/src/main/cpp/cpp-adapter.cpp +0 -6
  42. package/android/src/main/java/com/margelo/nitro/videotrim/VideoTrimPackage.kt +0 -22
  43. package/ios/AssetLoader.swift +0 -99
  44. package/ios/ErrorCode.swift +0 -17
  45. package/ios/ProgressAlertController.swift +0 -100
  46. package/ios/VideoTrim.swift +0 -67
  47. package/ios/VideoTrimImpl.swift +0 -957
  48. package/ios/VideoTrimmer.swift +0 -872
  49. package/ios/VideoTrimmerThumb.swift +0 -175
  50. package/ios/VideoTrimmerViewController.swift +0 -557
  51. package/lib/module/VideoTrim.nitro.js +0 -4
  52. package/lib/module/VideoTrim.nitro.js.map +0 -1
  53. package/lib/typescript/src/VideoTrim.nitro.d.ts +0 -257
  54. package/lib/typescript/src/VideoTrim.nitro.d.ts.map +0 -1
  55. package/nitrogen/generated/android/c++/JEditorConfig.hpp +0 -237
  56. package/nitrogen/generated/android/c++/JFileValidationResult.hpp +0 -61
  57. package/nitrogen/generated/android/c++/JFunc_void.hpp +0 -74
  58. package/nitrogen/generated/android/c++/JFunc_void_std__string_std__unordered_map_std__string__std__string_.hpp +0 -89
  59. package/nitrogen/generated/android/c++/JHybridVideoTrimSpec.cpp +0 -151
  60. package/nitrogen/generated/android/c++/JHybridVideoTrimSpec.hpp +0 -68
  61. package/nitrogen/generated/android/c++/JTrimOptions.hpp +0 -109
  62. package/nitrogen/generated/android/kotlin/com/margelo/nitro/videotrim/EditorConfig.kt +0 -72
  63. package/nitrogen/generated/android/kotlin/com/margelo/nitro/videotrim/FileValidationResult.kt +0 -28
  64. package/nitrogen/generated/android/kotlin/com/margelo/nitro/videotrim/Func_void.kt +0 -80
  65. package/nitrogen/generated/android/kotlin/com/margelo/nitro/videotrim/Func_void_std__string_std__unordered_map_std__string__std__string_.kt +0 -80
  66. package/nitrogen/generated/android/kotlin/com/margelo/nitro/videotrim/HybridVideoTrimSpec.kt +0 -86
  67. package/nitrogen/generated/android/kotlin/com/margelo/nitro/videotrim/TrimOptions.kt +0 -40
  68. package/nitrogen/generated/android/kotlin/com/margelo/nitro/videotrim/videotrimOnLoad.kt +0 -35
  69. package/nitrogen/generated/android/videotrim+autolinking.cmake +0 -78
  70. package/nitrogen/generated/android/videotrim+autolinking.gradle +0 -27
  71. package/nitrogen/generated/android/videotrimOnLoad.cpp +0 -50
  72. package/nitrogen/generated/android/videotrimOnLoad.hpp +0 -25
  73. package/nitrogen/generated/ios/VideoTrim+autolinking.rb +0 -60
  74. package/nitrogen/generated/ios/VideoTrim-Swift-Cxx-Bridge.cpp +0 -96
  75. package/nitrogen/generated/ios/VideoTrim-Swift-Cxx-Bridge.hpp +0 -374
  76. package/nitrogen/generated/ios/VideoTrim-Swift-Cxx-Umbrella.hpp +0 -56
  77. package/nitrogen/generated/ios/VideoTrimAutolinking.mm +0 -33
  78. package/nitrogen/generated/ios/VideoTrimAutolinking.swift +0 -25
  79. package/nitrogen/generated/ios/c++/HybridVideoTrimSpecSwift.cpp +0 -11
  80. package/nitrogen/generated/ios/c++/HybridVideoTrimSpecSwift.hpp +0 -127
  81. package/nitrogen/generated/ios/swift/EditorConfig.swift +0 -541
  82. package/nitrogen/generated/ios/swift/FileValidationResult.swift +0 -57
  83. package/nitrogen/generated/ios/swift/Func_void.swift +0 -46
  84. package/nitrogen/generated/ios/swift/Func_void_FileValidationResult.swift +0 -46
  85. package/nitrogen/generated/ios/swift/Func_void_bool.swift +0 -46
  86. package/nitrogen/generated/ios/swift/Func_void_double.swift +0 -46
  87. package/nitrogen/generated/ios/swift/Func_void_std__exception_ptr.swift +0 -46
  88. package/nitrogen/generated/ios/swift/Func_void_std__string.swift +0 -46
  89. package/nitrogen/generated/ios/swift/Func_void_std__string_std__unordered_map_std__string__std__string_.swift +0 -54
  90. package/nitrogen/generated/ios/swift/Func_void_std__vector_std__string_.swift +0 -46
  91. package/nitrogen/generated/ios/swift/HybridVideoTrimSpec.swift +0 -54
  92. package/nitrogen/generated/ios/swift/HybridVideoTrimSpec_cxx.swift +0 -241
  93. package/nitrogen/generated/ios/swift/TrimOptions.swift +0 -189
  94. package/nitrogen/generated/shared/c++/EditorConfig.hpp +0 -253
  95. package/nitrogen/generated/shared/c++/FileValidationResult.hpp +0 -77
  96. package/nitrogen/generated/shared/c++/HybridVideoTrimSpec.cpp +0 -27
  97. package/nitrogen/generated/shared/c++/HybridVideoTrimSpec.hpp +0 -80
  98. package/nitrogen/generated/shared/c++/TrimOptions.hpp +0 -125
  99. package/src/VideoTrim.nitro.ts +0 -263
@@ -0,0 +1,533 @@
1
+ #import "VideoTrimmerViewController.h"
2
+ #import "VideoTrimmer.h"
3
+ #import <AVFoundation/AVFoundation.h>
4
+
5
+
6
+ @implementation VideoTrimmerViewController
7
+
8
+ - (instancetype)init {
9
+ if (self = [super init]) {
10
+ self.cancelButtonText = @"Cancel";
11
+ self.saveButtonText = @"Save";
12
+ self.headerTextSize = 16;
13
+ self.headerTextColor = 0xFFFFFF;
14
+ self.autoplay = NO;
15
+ self.jumpToPositionOnLoad = 0;
16
+ self.enableHapticFeedback = YES;
17
+ self.chaseTime = kCMTimeZero;
18
+ self.isSeekInProgress = NO;
19
+ self.playerController = [[AVPlayerViewController alloc] init];
20
+ self.loadingIndicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleMedium];
21
+ }
22
+ return self;
23
+ }
24
+
25
+ - (void)setAsset:(AVAsset *)asset {
26
+ NSLog(@"📱 VideoTrimmerViewController setAsset called with: %@", asset);
27
+
28
+ _asset = asset;
29
+ if (asset) {
30
+ [self setupVideoTrimmer];
31
+ [self setupPlayerController];
32
+ [self setupTimeObserver];
33
+ [self updateLabels];
34
+ }
35
+ }
36
+
37
+ - (AVPlayer *)player {
38
+ return self.playerController.player;
39
+ }
40
+
41
+ - (void)onAssetFailToLoad {
42
+ [self.loadingIndicator stopAnimating];
43
+ [self.btnStackView removeArrangedSubview:self.loadingIndicator];
44
+ [self.loadingIndicator removeFromSuperview];
45
+
46
+ UIView *imageViewContainer = [[UIView alloc] init];
47
+ UIImageView *imageView = [[UIImageView alloc] initWithImage:[UIImage systemImageNamed:@"exclamationmark.triangle.fill"]];
48
+ imageView.tintColor = [UIColor systemYellowColor];
49
+ imageView.translatesAutoresizingMaskIntoConstraints = NO;
50
+
51
+ [imageViewContainer addSubview:imageView];
52
+ [NSLayoutConstraint activateConstraints:@[
53
+ [imageView.widthAnchor constraintEqualToConstant:36],
54
+ [imageView.heightAnchor constraintEqualToConstant:36],
55
+ [imageView.centerXAnchor constraintEqualToAnchor:imageViewContainer.centerXAnchor],
56
+ [imageView.centerYAnchor constraintEqualToAnchor:imageViewContainer.centerYAnchor]
57
+ ]];
58
+ imageViewContainer.alpha = 0;
59
+
60
+ [self.btnStackView insertArrangedSubview:imageViewContainer atIndex:1];
61
+
62
+ [UIView animateWithDuration:0.25 animations:^{
63
+ imageViewContainer.alpha = 1;
64
+ }];
65
+ }
66
+
67
+ - (void)didBeginTrimmingFromStart:(VideoTrimmer *)sender {
68
+ [self handleBeforeProgressChange];
69
+ }
70
+
71
+ - (void)leadingGrabberChanged:(VideoTrimmer *)sender {
72
+ [self handleProgressChanged:self.trimmer.selectedRange.start];
73
+ }
74
+
75
+ - (void)didEndTrimmingFromStart:(VideoTrimmer *)sender {
76
+ [self handleTrimmingEnd:YES];
77
+ }
78
+
79
+ - (void)didBeginTrimmingFromEnd:(VideoTrimmer *)sender {
80
+ [self handleBeforeProgressChange];
81
+ }
82
+
83
+ - (void)trailingGrabberChanged:(VideoTrimmer *)sender {
84
+ [self handleProgressChanged:CMTimeRangeGetEnd(self.trimmer.selectedRange)];
85
+ }
86
+
87
+ - (void)didEndTrimmingFromEnd:(VideoTrimmer *)sender {
88
+ [self handleTrimmingEnd:NO];
89
+ }
90
+
91
+ - (void)didBeginScrubbing:(VideoTrimmer *)sender {
92
+ [self handleBeforeProgressChange];
93
+ }
94
+
95
+ - (void)didEndScrubbing:(VideoTrimmer *)sender {
96
+ [self updateLabels];
97
+ }
98
+
99
+ - (void)progressDidChanged:(VideoTrimmer *)sender {
100
+ [self handleProgressChanged:self.trimmer.progress];
101
+ }
102
+
103
+ - (void)updateLabels {
104
+ self.leadingTrimLabel.text = [self displayStringFromCMTime:self.trimmer.selectedRange.start];
105
+ self.currentTimeLabel.text = [self displayStringFromCMTime:self.trimmer.progress];
106
+ self.trailingTrimLabel.text = [self displayStringFromCMTime:CMTimeRangeGetEnd(self.trimmer.selectedRange)];
107
+ }
108
+
109
+ - (NSString *)displayStringFromCMTime:(CMTime)time {
110
+ NSTimeInterval offset = CMTimeGetSeconds(time);
111
+ double numberOfNanosecondsFloat = (offset - (NSTimeInterval)((int)offset)) * 100.0;
112
+ int nanoseconds = (int)numberOfNanosecondsFloat;
113
+
114
+ NSDateComponentsFormatter *formatter = [[NSDateComponentsFormatter alloc] init];
115
+ formatter.unitsStyle = NSDateComponentsFormatterUnitsStylePositional;
116
+ formatter.zeroFormattingBehavior = NSDateComponentsFormatterZeroFormattingBehaviorPad;
117
+ formatter.allowedUnits = NSCalendarUnitMinute | NSCalendarUnitSecond;
118
+
119
+ NSString *timeString = [formatter stringFromTimeInterval:offset] ?: @"00:00";
120
+ return [NSString stringWithFormat:@"%@.%02d", timeString, nanoseconds];
121
+ }
122
+
123
+ - (void)handleBeforeProgressChange {
124
+ [self updateLabels];
125
+ [self.player pause];
126
+ [self setPlayBtnIcon];
127
+ }
128
+
129
+ - (void)handleProgressChanged:(CMTime)time {
130
+ [self updateLabels];
131
+ [self seek:time];
132
+ }
133
+
134
+ - (void)handleTrimmingEnd:(BOOL)start {
135
+ self.trimmer.progress = start ? self.trimmer.selectedRange.start : CMTimeRangeGetEnd(self.trimmer.selectedRange);
136
+ [self updateLabels];
137
+ [self seek:self.trimmer.progress];
138
+ }
139
+
140
+ - (void)viewDidLoad {
141
+ [super viewDidLoad];
142
+ [self setupView];
143
+ [self setupButtons];
144
+ [self setupTimeLabels];
145
+ }
146
+
147
+ - (void)viewWillDisappear:(BOOL)animated {
148
+ [super viewWillDisappear:animated];
149
+
150
+ if (!self.asset) return;
151
+ [self.player pause];
152
+
153
+ [self.player removeObserver:self forKeyPath:@"status"];
154
+
155
+ if (self.timeObserverToken) {
156
+ [self.player removeTimeObserver:self.timeObserverToken];
157
+ self.timeObserverToken = nil;
158
+ }
159
+
160
+ [[NSNotificationCenter defaultCenter] removeObserver:self name:AVPlayerItemDidPlayToEndTimeNotification object:self.player.currentItem];
161
+
162
+ self.playerController.player = nil;
163
+ [self.playerController dismissViewControllerAnimated:NO completion:nil];
164
+ }
165
+
166
+ - (void)pausePlayer {
167
+ [self.player pause];
168
+ [self setPlayBtnIcon];
169
+ }
170
+
171
+ - (void)togglePlay:(UIButton *)sender {
172
+ if (self.player.timeControlStatus == AVPlayerTimeControlStatusPlaying) {
173
+ [self.player pause];
174
+ } else {
175
+ if (CMTimeCompare(self.trimmer.progress, CMTimeRangeGetEnd(self.trimmer.selectedRange)) != -1) {
176
+ self.trimmer.progress = self.trimmer.selectedRange.start;
177
+ [self seek:self.trimmer.progress];
178
+ }
179
+ [self.player play];
180
+ }
181
+ [self setPlayBtnIcon];
182
+ }
183
+
184
+ - (void)onSaveBtnClicked {
185
+ if (self.saveBtnClicked) {
186
+ self.saveBtnClicked(self.trimmer.selectedRange);
187
+ }
188
+ }
189
+
190
+ - (void)onCancelBtnClicked {
191
+ if (self.cancelBtnClicked) {
192
+ self.cancelBtnClicked();
193
+ }
194
+ }
195
+
196
+ - (UIColor *)colorFromHex:(double)hex defaultColor:(UIColor *)defaultColor {
197
+ int32_t hexValue = (int32_t)hex;
198
+ CGFloat red = ((hexValue >> 16) & 0xFF) / 255.0;
199
+ CGFloat green = ((hexValue >> 8) & 0xFF) / 255.0;
200
+ CGFloat blue = (hexValue & 0xFF) / 255.0;
201
+ CGFloat alpha = (hexValue > 0xFFFFFF) ? ((hexValue >> 24) & 0xFF) / 255.0 : 1.0;
202
+ return [UIColor colorWithRed:red green:green blue:blue alpha:alpha];
203
+ }
204
+
205
+ - (UIButton *)createButtonWithTitle:(NSString *)title
206
+ font:(UIFont *)font
207
+ titleColor:(UIColor *)titleColor
208
+ image:(UIImage *)image
209
+ tintColor:(UIColor *)tintColor
210
+ target:(id)target
211
+ action:(SEL)action {
212
+ UIButton *button = [UIButton buttonWithType:UIButtonTypeSystem];
213
+ if (title) [button setTitle:title forState:UIControlStateNormal];
214
+ if (image) [button setImage:image forState:UIControlStateNormal];
215
+ if (font) button.titleLabel.font = font;
216
+ if (titleColor) [button setTitleColor:titleColor forState:UIControlStateNormal];
217
+ if (tintColor) button.tintColor = tintColor;
218
+ [button addTarget:target action:action forControlEvents:UIControlEventTouchUpInside];
219
+ return button;
220
+ }
221
+
222
+ - (UILabel *)createLabelWithAlignment:(NSTextAlignment)alignment textColor:(UIColor *)color {
223
+ UILabel *label = [[UILabel alloc] init];
224
+ label.font = [UIFont preferredFontForTextStyle:UIFontTextStyleCaption1];
225
+ label.textAlignment = alignment;
226
+ label.textColor = color;
227
+ return label;
228
+ }
229
+
230
+ - (void)setupView {
231
+ self.overrideUserInterfaceStyle = UIUserInterfaceStyleDark;
232
+ self.view.backgroundColor = [UIColor blackColor];
233
+
234
+ if (self.headerText) {
235
+ self.headerView = [[UIView alloc] init];
236
+ self.headerView.translatesAutoresizingMaskIntoConstraints = NO;
237
+ [self.view addSubview:self.headerView];
238
+
239
+ UITextView *headerTextView = [[UITextView alloc] init];
240
+ headerTextView.text = self.headerText;
241
+ headerTextView.textAlignment = NSTextAlignmentCenter;
242
+ headerTextView.textColor = [self colorFromHex:self.headerTextColor defaultColor:[UIColor whiteColor]];
243
+ headerTextView.font = [UIFont systemFontOfSize:self.headerTextSize];
244
+ headerTextView.translatesAutoresizingMaskIntoConstraints = NO;
245
+ [self.headerView addSubview:headerTextView];
246
+
247
+ [NSLayoutConstraint activateConstraints:@[
248
+ [self.headerView.topAnchor constraintEqualToAnchor:self.view.safeAreaLayoutGuide.topAnchor],
249
+ [self.headerView.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor],
250
+ [self.headerView.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor],
251
+ [self.headerView.heightAnchor constraintGreaterThanOrEqualToConstant:50],
252
+
253
+ [headerTextView.topAnchor constraintEqualToAnchor:self.headerView.topAnchor],
254
+ [headerTextView.bottomAnchor constraintEqualToAnchor:self.headerView.bottomAnchor],
255
+ [headerTextView.leadingAnchor constraintEqualToAnchor:self.headerView.leadingAnchor],
256
+ [headerTextView.trailingAnchor constraintEqualToAnchor:self.headerView.trailingAnchor],
257
+ ]];
258
+
259
+ [self.view layoutIfNeeded];
260
+ }
261
+ }
262
+
263
+ - (void)setupButtons {
264
+ self.cancelBtn = [self createButtonWithTitle:self.cancelButtonText
265
+ font:[UIFont systemFontOfSize:18]
266
+ titleColor:[UIColor whiteColor]
267
+ image:nil
268
+ tintColor:nil
269
+ target:self
270
+ action:@selector(onCancelBtnClicked)];
271
+
272
+ self.playBtn = [self createButtonWithTitle:nil
273
+ font:nil
274
+ titleColor:nil
275
+ image:[UIImage systemImageNamed:@"play.fill"]
276
+ tintColor:[UIColor whiteColor]
277
+ target:self
278
+ action:@selector(togglePlay:)];
279
+ self.playBtn.alpha = 0;
280
+ self.playBtn.enabled = NO;
281
+
282
+ self.saveBtn = [self createButtonWithTitle:self.saveButtonText
283
+ font:[UIFont systemFontOfSize:18]
284
+ titleColor:[UIColor systemBlueColor]
285
+ image:nil
286
+ tintColor:nil
287
+ target:self
288
+ action:@selector(onSaveBtnClicked)];
289
+ self.saveBtn.alpha = 0;
290
+ self.saveBtn.enabled = NO;
291
+
292
+ [self.loadingIndicator startAnimating];
293
+
294
+ self.btnStackView = [[UIStackView alloc] initWithArrangedSubviews:@[self.cancelBtn, self.loadingIndicator, self.saveBtn]];
295
+ self.btnStackView.axis = UILayoutConstraintAxisHorizontal;
296
+ self.btnStackView.alignment = UIStackViewAlignmentCenter;
297
+ self.btnStackView.distribution = UIStackViewDistributionFillEqually;
298
+ self.btnStackView.spacing = UIStackViewSpacingUseSystem;
299
+ self.btnStackView.translatesAutoresizingMaskIntoConstraints = NO;
300
+
301
+ [self.view addSubview:self.btnStackView];
302
+
303
+ [NSLayoutConstraint activateConstraints:@[
304
+ [self.btnStackView.leadingAnchor constraintEqualToAnchor:self.view.safeAreaLayoutGuide.leadingAnchor constant:16],
305
+ [self.btnStackView.trailingAnchor constraintEqualToAnchor:self.view.safeAreaLayoutGuide.trailingAnchor constant:-16],
306
+ [self.btnStackView.bottomAnchor constraintEqualToAnchor:self.view.safeAreaLayoutGuide.bottomAnchor constant:-16]
307
+ ]];
308
+ }
309
+
310
+ - (void)setupTimeLabels {
311
+ self.leadingTrimLabel = [self createLabelWithAlignment:NSTextAlignmentLeft textColor:[UIColor whiteColor]];
312
+ self.leadingTrimLabel.text = @"00:00.000";
313
+
314
+ self.currentTimeLabel = [self createLabelWithAlignment:NSTextAlignmentCenter textColor:[UIColor whiteColor]];
315
+ self.currentTimeLabel.text = @"00:00.000";
316
+
317
+ self.trailingTrimLabel = [self createLabelWithAlignment:NSTextAlignmentRight textColor:[UIColor whiteColor]];
318
+ self.trailingTrimLabel.text = @"00:00.000";
319
+
320
+ self.timingStackView = [[UIStackView alloc] initWithArrangedSubviews:@[self.leadingTrimLabel, self.currentTimeLabel, self.trailingTrimLabel]];
321
+ self.timingStackView.axis = UILayoutConstraintAxisHorizontal;
322
+ self.timingStackView.alignment = UIStackViewAlignmentFill;
323
+ self.timingStackView.distribution = UIStackViewDistributionFillEqually;
324
+ self.timingStackView.spacing = UIStackViewSpacingUseSystem;
325
+ self.timingStackView.translatesAutoresizingMaskIntoConstraints = NO;
326
+
327
+ [self.view addSubview:self.timingStackView];
328
+
329
+ [NSLayoutConstraint activateConstraints:@[
330
+ [self.timingStackView.leadingAnchor constraintEqualToAnchor:self.view.safeAreaLayoutGuide.leadingAnchor constant:16],
331
+ [self.timingStackView.trailingAnchor constraintEqualToAnchor:self.view.safeAreaLayoutGuide.trailingAnchor constant:-16],
332
+ [self.timingStackView.bottomAnchor constraintEqualToAnchor:self.btnStackView.topAnchor constant:-8]
333
+ ]];
334
+ }
335
+
336
+ - (void)setupVideoTrimmer {
337
+ self.trimmer = [[VideoTrimmer alloc] init];
338
+ self.trimmer.asset = self.asset;
339
+ self.trimmer.minimumDuration = CMTimeMake(1 * 600, 600); // 1 second
340
+ self.trimmer.enableHapticFeedback = self.enableHapticFeedback;
341
+
342
+ if (self.maximumDuration > 0) {
343
+ CMTime maxDuration = CMTimeMake(MAX(1, self.maximumDuration) * 600, 600);
344
+ if (CMTimeCompare(maxDuration, self.asset.duration) > 0) {
345
+ maxDuration = self.asset.duration;
346
+ }
347
+ self.trimmer.maximumDuration = maxDuration;
348
+ self.trimmer.selectedRange = CMTimeRangeMake(kCMTimeZero, maxDuration);
349
+ }
350
+
351
+ if (self.minimumDuration > 0) {
352
+ self.trimmer.minimumDuration = CMTimeMake(MAX(1, self.minimumDuration) * 600, 600);
353
+ }
354
+
355
+ // Add target-action for all VideoTrimmer custom events
356
+ [self.trimmer addTarget:self action:@selector(didBeginScrubbing:) forControlEvents:[VideoTrimmer didBeginScrubbing]];
357
+ [self.trimmer addTarget:self action:@selector(didEndScrubbing:) forControlEvents:[VideoTrimmer didEndScrubbing]];
358
+ [self.trimmer addTarget:self action:@selector(progressDidChanged:) forControlEvents:[VideoTrimmer progressChanged]];
359
+
360
+ [self.trimmer addTarget:self action:@selector(didBeginTrimmingFromStart:) forControlEvents:[VideoTrimmer didBeginTrimmingFromStart]];
361
+ [self.trimmer addTarget:self action:@selector(leadingGrabberChanged:) forControlEvents:[VideoTrimmer leadingGrabberChanged]];
362
+ [self.trimmer addTarget:self action:@selector(didEndTrimmingFromStart:) forControlEvents:[VideoTrimmer didEndTrimmingFromStart]];
363
+
364
+ [self.trimmer addTarget:self action:@selector(didBeginTrimmingFromEnd:) forControlEvents:[VideoTrimmer didBeginTrimmingFromEnd]];
365
+ [self.trimmer addTarget:self action:@selector(trailingGrabberChanged:) forControlEvents:[VideoTrimmer trailingGrabberChanged]];
366
+ [self.trimmer addTarget:self action:@selector(didEndTrimmingFromEnd:) forControlEvents:[VideoTrimmer didEndTrimmingFromEnd]];
367
+
368
+ self.trimmer.alpha = 0;
369
+ self.trimmer.translatesAutoresizingMaskIntoConstraints = NO;
370
+ [self.view addSubview:self.trimmer];
371
+
372
+ [NSLayoutConstraint activateConstraints:@[
373
+ [self.trimmer.leadingAnchor constraintEqualToAnchor:self.view.safeAreaLayoutGuide.leadingAnchor],
374
+ [self.trimmer.trailingAnchor constraintEqualToAnchor:self.view.safeAreaLayoutGuide.trailingAnchor],
375
+ [self.trimmer.bottomAnchor constraintEqualToAnchor:self.timingStackView.topAnchor constant:-16],
376
+ [self.trimmer.heightAnchor constraintEqualToConstant:50]
377
+ ]];
378
+
379
+ [UIView animateWithDuration:0.25 animations:^{
380
+ self.trimmer.alpha = 1;
381
+ }];
382
+ }
383
+
384
+ - (void)setupPlayerController {
385
+ self.playerController.showsPlaybackControls = NO;
386
+ if (@available(iOS 16.0, *)) {
387
+ self.playerController.allowsVideoFrameAnalysis = NO;
388
+ }
389
+ self.playerController.player = [[AVPlayer alloc] init];
390
+ [self.player replaceCurrentItemWithPlayerItem:[AVPlayerItem playerItemWithAsset:self.asset]];
391
+
392
+ [self.player addObserver:self forKeyPath:@"status" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionInitial context:nil];
393
+
394
+ NSError *error = nil;
395
+ [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback mode:AVAudioSessionModeDefault options:0 error:&error];
396
+ if (error) {
397
+ NSLog(@"AVAudioSession error: %@", error);
398
+ }
399
+
400
+ [self addChildViewController:self.playerController];
401
+ [self.view addSubview:self.playerController.view];
402
+ self.playerController.view.translatesAutoresizingMaskIntoConstraints = NO;
403
+
404
+ [NSLayoutConstraint activateConstraints:@[
405
+ [self.playerController.view.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor],
406
+ [self.playerController.view.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor],
407
+ [self.playerController.view.topAnchor constraintEqualToAnchor:self.headerView ? self.headerView.bottomAnchor : self.view.safeAreaLayoutGuide.topAnchor],
408
+ [self.playerController.view.bottomAnchor constraintEqualToAnchor:self.trimmer.topAnchor constant:-16]
409
+ ]];
410
+
411
+ [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(playerDidFinishPlaying:) name:AVPlayerItemDidPlayToEndTimeNotification object:self.player.currentItem];
412
+ }
413
+
414
+ - (void)playerDidFinishPlaying:(NSNotification *)notification {
415
+ [self.playBtn setImage:[UIImage systemImageNamed:@"play.fill"] forState:UIControlStateNormal];
416
+ }
417
+
418
+ - (void)setupTimeObserver {
419
+ __weak __typeof__(self) weakSelf = self;
420
+ self.timeObserverToken = [self.player addPeriodicTimeObserverForInterval:CMTimeMake(1, 30) queue:dispatch_get_main_queue() usingBlock:^(CMTime time) {
421
+ if (weakSelf.player.timeControlStatus != AVPlayerTimeControlStatusPlaying) {
422
+ return;
423
+ }
424
+
425
+ weakSelf.trimmer.progress = time;
426
+
427
+ if (CMTimeCompare(weakSelf.trimmer.progress, CMTimeRangeGetEnd(weakSelf.trimmer.selectedRange)) == 1) {
428
+ [weakSelf.player pause];
429
+ weakSelf.trimmer.progress = CMTimeRangeGetEnd(weakSelf.trimmer.selectedRange);
430
+ [weakSelf seek:CMTimeRangeGetEnd(weakSelf.trimmer.selectedRange)];
431
+ }
432
+
433
+ weakSelf.currentTimeLabel.text = [weakSelf displayStringFromCMTime:weakSelf.trimmer.progress];
434
+ [weakSelf setPlayBtnIcon];
435
+ }];
436
+ }
437
+
438
+ - (void)setPlayBtnIcon {
439
+ UIImage *icon = (self.player.timeControlStatus == AVPlayerTimeControlStatusPlaying) ?
440
+ [UIImage systemImageNamed:@"pause.fill"] :
441
+ [UIImage systemImageNamed:@"play.fill"];
442
+ [self.playBtn setImage:icon forState:UIControlStateNormal];
443
+ }
444
+
445
+ - (void)seek:(CMTime)time {
446
+ [self seekSmoothlyToTime:time];
447
+ }
448
+
449
+ - (void)seekSmoothlyToTime:(CMTime)newChaseTime {
450
+ if (CMTimeCompare(newChaseTime, self.chaseTime) != 0) {
451
+ self.chaseTime = newChaseTime;
452
+
453
+ if (!self.isSeekInProgress) {
454
+ [self trySeekToChaseTime];
455
+ }
456
+ }
457
+ }
458
+
459
+ - (void)trySeekToChaseTime {
460
+ if (self.player.status != AVPlayerStatusReadyToPlay) return;
461
+ [self actuallySeekToTime];
462
+ }
463
+
464
+ - (void)actuallySeekToTime {
465
+ self.isSeekInProgress = YES;
466
+ CMTime seekTimeInProgress = self.chaseTime;
467
+
468
+ [self.player seekToTime:seekTimeInProgress toleranceBefore:kCMTimeZero toleranceAfter:kCMTimeZero completionHandler:^(BOOL finished) {
469
+ if (CMTimeCompare(seekTimeInProgress, self.chaseTime) == 0) {
470
+ self.isSeekInProgress = NO;
471
+ } else {
472
+ [self trySeekToChaseTime];
473
+ }
474
+ }];
475
+ }
476
+
477
+ - (void)configureWithConfig:(JS::NativeVideoTrim::EditorConfig)config {
478
+ if (config.maxDuration() > 0) {
479
+ self.maximumDuration = (NSInteger)config.maxDuration();
480
+ }
481
+
482
+ if (config.minDuration() > 0) {
483
+ self.minimumDuration = (NSInteger)config.minDuration();
484
+ }
485
+
486
+ self.cancelButtonText = config.cancelButtonText();
487
+ self.saveButtonText = config.saveButtonText();
488
+ self.jumpToPositionOnLoad = config.jumpToPositionOnLoad();
489
+
490
+ self.enableHapticFeedback = config.enableHapticFeedback();
491
+ self.autoplay = config.autoplay();
492
+
493
+ NSString *headerText = config.headerText();
494
+ if (headerText && headerText.length > 0) {
495
+ self.headerText = headerText;
496
+ self.headerTextSize = (NSInteger)config.headerTextSize();
497
+ self.headerTextColor = config.headerTextColor();
498
+ }
499
+ }
500
+
501
+ - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
502
+ if ([keyPath isEqualToString:@"status"]) {
503
+ if (self.player.status == AVPlayerStatusReadyToPlay) {
504
+ [self.loadingIndicator stopAnimating];
505
+ [self.btnStackView removeArrangedSubview:self.loadingIndicator];
506
+ [self.loadingIndicator removeFromSuperview];
507
+ [self.btnStackView insertArrangedSubview:self.playBtn atIndex:1];
508
+
509
+ [UIView animateWithDuration:0.25 animations:^{
510
+ self.playBtn.alpha = 1;
511
+ self.playBtn.enabled = YES;
512
+ self.saveBtn.alpha = 1;
513
+ self.saveBtn.enabled = YES;
514
+ }];
515
+
516
+ if (self.jumpToPositionOnLoad > 0) {
517
+ double duration = CMTimeGetSeconds(self.asset.duration) * 1000;
518
+ double time = (self.jumpToPositionOnLoad > duration) ? duration : self.jumpToPositionOnLoad;
519
+ CMTime cmtime = CMTimeMake((CMTimeValue)time, 1000);
520
+
521
+ [self seek:cmtime];
522
+ self.trimmer.progress = cmtime;
523
+ self.currentTimeLabel.text = [self displayStringFromCMTime:self.trimmer.progress];
524
+ }
525
+
526
+ if (self.autoplay) {
527
+ [self togglePlay:self.playBtn];
528
+ }
529
+ }
530
+ }
531
+ }
532
+
533
+ @end
@@ -0,0 +1,5 @@
1
+ "use strict";
2
+
3
+ import { TurboModuleRegistry } from 'react-native';
4
+ export default TurboModuleRegistry.getEnforcing('VideoTrim');
5
+ //# sourceMappingURL=NativeVideoTrim.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":["TurboModuleRegistry","getEnforcing"],"sourceRoot":"../../src","sources":["NativeVideoTrim.ts"],"mappings":";;AACA,SAASA,mBAAmB,QAAQ,cAAc;AA+GlD,eAAeA,mBAAmB,CAACC,YAAY,CAAO,WAAW,CAAC","ignoreList":[]}
@@ -1,8 +1,8 @@
1
1
  "use strict";
2
2
 
3
- import { NitroModules } from 'react-native-nitro-modules';
3
+ import VideoTrim from "./NativeVideoTrim.js";
4
4
  import { processColor } from 'react-native';
5
- const VideoTrimHybridObject = NitroModules.createHybridObject('VideoTrim');
5
+ export * from "./NativeVideoTrim.js";
6
6
  function createBaseOptions(overrides = {}) {
7
7
  return {
8
8
  saveToPhoto: false,
@@ -25,8 +25,7 @@ function createEditorConfig(overrides = {}) {
25
25
  return {
26
26
  enableHapticFeedback: true,
27
27
  maxDuration: -1,
28
- // Adjust default as needed
29
- minDuration: 1000,
28
+ minDuration: -1,
30
29
  cancelButtonText: 'Cancel',
31
30
  saveButtonText: 'Save',
32
31
  enableCancelDialog: true,
@@ -42,7 +41,6 @@ function createEditorConfig(overrides = {}) {
42
41
  trimmingText: 'Trimming video...',
43
42
  fullScreenModalIOS: false,
44
43
  autoplay: false,
45
- // Adjust default as needed
46
44
  jumpToPositionOnLoad: -1,
47
45
  closeWhenFinish: true,
48
46
  enableCancelTrimming: true,
@@ -53,7 +51,6 @@ function createEditorConfig(overrides = {}) {
53
51
  cancelTrimmingDialogCancelText: 'Close',
54
52
  cancelTrimmingDialogConfirmText: 'Proceed',
55
53
  headerText: '',
56
- // Adjust default as needed
57
54
  headerTextSize: 16,
58
55
  headerTextColor: processColor('white'),
59
56
  alertOnFailToLoad: true,
@@ -72,71 +69,72 @@ function createTrimOptions(overrides = {}) {
72
69
  ...overrides
73
70
  };
74
71
  }
75
- function noop() {}
76
72
 
77
73
  /**
78
- * Delete a file
74
+ * Show video editor
79
75
  *
80
- * @param {string} videoPath: absolute non-empty file path to edit
76
+ * @param {string} filePath: absolute non-empty file path to edit
81
77
  * @param {EditorConfig} config: editor configuration
82
- * @returns {void} A **Promise** which resolves `void`
78
+ * @param {Function} onEvent: event callback
79
+ * @returns {void}
83
80
  */
84
- export function showEditor(filePath, config, onEvent) {
81
+ export function showEditor(filePath, config) {
85
82
  const {
86
83
  headerTextColor
87
84
  } = config;
88
85
  const color = processColor(headerTextColor || 'white');
89
- VideoTrimHybridObject.showEditor(filePath, createEditorConfig({
86
+ VideoTrim.showEditor(filePath, createEditorConfig({
90
87
  ...config,
91
88
  headerTextColor: color
92
- }), onEvent || noop);
89
+ }));
93
90
  }
94
91
 
95
92
  /**
96
- * Clean output files generated at all time
93
+ * List output files generated at all time
97
94
  *
98
95
  * @returns {Promise<string[]>} A **Promise** which resolves to array of files
99
96
  */
100
97
  export function listFiles() {
101
- return VideoTrimHybridObject.listFiles();
98
+ return VideoTrim.listFiles();
102
99
  }
103
100
 
104
101
  /**
105
102
  * Clean output files generated at all time
106
103
  *
107
- * @returns {Promise} A **Promise** which resolves to number of deleted files
104
+ * @returns {Promise<number>} A **Promise** which resolves to number of deleted files
108
105
  */
109
106
  export function cleanFiles() {
110
- return VideoTrimHybridObject.cleanFiles();
107
+ return VideoTrim.cleanFiles();
111
108
  }
112
109
 
113
110
  /**
114
111
  * Delete a file
115
112
  *
116
113
  * @param {string} filePath: absolute non-empty file path to delete
117
- * @returns {Promise} A **Promise** which resolves `true` if successful
114
+ * @returns {Promise<boolean>} A **Promise** which resolves `true` if successful
118
115
  */
119
116
  export function deleteFile(filePath) {
120
117
  if (!filePath?.trim().length) {
121
118
  throw new Error('File path cannot be empty!');
122
119
  }
123
- return VideoTrimHybridObject.deleteFile(filePath);
120
+ return VideoTrim.deleteFile(filePath);
124
121
  }
125
122
 
126
123
  /**
127
124
  * Close editor
128
125
  */
129
- export function closeEditor(onComplete) {
130
- return VideoTrimHybridObject.closeEditor(onComplete || noop);
126
+ export function closeEditor() {
127
+ return VideoTrim.closeEditor();
131
128
  }
132
129
 
133
130
  /**
134
131
  * Check if a file is valid audio or video file
135
132
  *
136
- * @returns {Promise} A **Promise** which resolves file info if successful
133
+ * @param {string} url: file path to validate
134
+ * @returns {Promise<FileValidationResult>} A **Promise** which resolves file info if successful
137
135
  */
138
136
  export function isValidFile(url) {
139
- return VideoTrimHybridObject.isValidFile(url);
137
+ return VideoTrim.isValidFile(url);
140
138
  }
141
139
 
142
140
  /**
@@ -147,6 +145,8 @@ export function isValidFile(url) {
147
145
  * @returns {Promise<string>} A **Promise** which resolves to the trimmed file path
148
146
  */
149
147
  export function trim(url, options) {
150
- return VideoTrimHybridObject.trim(url, createTrimOptions(options));
148
+ return VideoTrim.trim(url, createTrimOptions(options));
151
149
  }
150
+ export * from "./NativeVideoTrim.js";
151
+ export default VideoTrim;
152
152
  //# sourceMappingURL=index.js.map