react-native-video-trim 4.1.0 → 5.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +1 -1
- package/README.md +89 -76
- package/VideoTrim.podspec +3 -3
- package/android/build.gradle +6 -53
- package/android/gradle.properties +1 -1
- package/android/src/main/AndroidManifest.xml +1 -1
- package/android/src/main/java/com/{margelo/nitro/videotrim/VideoTrim.kt → videotrim/VideoTrimModule.kt} +246 -232
- package/android/src/main/java/com/videotrim/VideoTrimPackage.kt +33 -0
- package/android/src/main/java/com/{margelo/nitro/videotrim → videotrim}/enums/ErrorCode.java +1 -1
- package/android/src/main/java/com/{margelo/nitro/videotrim → videotrim}/interfaces/IVideoTrimmerView.java +1 -1
- package/android/src/main/java/com/{margelo/nitro/videotrim → videotrim}/interfaces/VideoTrimListener.java +5 -4
- package/android/src/main/java/com/{margelo/nitro/videotrim → videotrim}/utils/MediaMetadataUtil.java +1 -1
- package/android/src/main/java/com/{margelo/nitro/videotrim → videotrim}/utils/StorageUtil.java +1 -1
- package/android/src/main/java/com/{margelo/nitro/videotrim → videotrim}/utils/VideoTrimmerUtil.java +20 -18
- package/android/src/main/java/com/{margelo/nitro/videotrim → videotrim}/widgets/VideoTrimmerView.java +44 -45
- package/ios/AssetLoader.h +19 -0
- package/ios/AssetLoader.mm +87 -0
- package/ios/ErrorCode.h +9 -0
- package/ios/ProgressAlertController.h +15 -0
- package/ios/ProgressAlertController.mm +78 -0
- package/ios/VideoTrim.h +31 -0
- package/ios/VideoTrim.mm +663 -0
- package/ios/VideoTrimmer.h +67 -0
- package/ios/VideoTrimmer.mm +863 -0
- package/ios/VideoTrimmerThumb.h +23 -0
- package/ios/VideoTrimmerThumb.mm +175 -0
- package/ios/VideoTrimmerViewController.h +52 -0
- package/ios/VideoTrimmerViewController.mm +533 -0
- package/lib/module/NativeVideoTrim.js +5 -0
- package/lib/module/NativeVideoTrim.js.map +1 -0
- package/lib/module/index.js +22 -24
- package/lib/module/index.js.map +1 -1
- package/lib/typescript/src/NativeVideoTrim.d.ts +107 -0
- package/lib/typescript/src/NativeVideoTrim.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +13 -10
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/package.json +15 -18
- package/src/NativeVideoTrim.ts +113 -0
- package/src/index.tsx +26 -31
- package/android/CMakeLists.txt +0 -24
- package/android/src/main/cpp/cpp-adapter.cpp +0 -6
- package/android/src/main/java/com/margelo/nitro/videotrim/VideoTrimPackage.kt +0 -22
- package/ios/AssetLoader.swift +0 -99
- package/ios/ErrorCode.swift +0 -17
- package/ios/ProgressAlertController.swift +0 -100
- package/ios/VideoTrim.swift +0 -67
- package/ios/VideoTrimImpl.swift +0 -957
- package/ios/VideoTrimmer.swift +0 -872
- package/ios/VideoTrimmerThumb.swift +0 -175
- package/ios/VideoTrimmerViewController.swift +0 -557
- package/lib/module/VideoTrim.nitro.js +0 -4
- package/lib/module/VideoTrim.nitro.js.map +0 -1
- package/lib/typescript/src/VideoTrim.nitro.d.ts +0 -257
- package/lib/typescript/src/VideoTrim.nitro.d.ts.map +0 -1
- package/nitrogen/generated/android/c++/JEditorConfig.hpp +0 -237
- package/nitrogen/generated/android/c++/JFileValidationResult.hpp +0 -61
- package/nitrogen/generated/android/c++/JFunc_void.hpp +0 -74
- package/nitrogen/generated/android/c++/JFunc_void_std__string_std__unordered_map_std__string__std__string_.hpp +0 -89
- package/nitrogen/generated/android/c++/JHybridVideoTrimSpec.cpp +0 -151
- package/nitrogen/generated/android/c++/JHybridVideoTrimSpec.hpp +0 -68
- package/nitrogen/generated/android/c++/JTrimOptions.hpp +0 -109
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/videotrim/EditorConfig.kt +0 -72
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/videotrim/FileValidationResult.kt +0 -28
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/videotrim/Func_void.kt +0 -80
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/videotrim/Func_void_std__string_std__unordered_map_std__string__std__string_.kt +0 -80
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/videotrim/HybridVideoTrimSpec.kt +0 -86
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/videotrim/TrimOptions.kt +0 -40
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/videotrim/videotrimOnLoad.kt +0 -35
- package/nitrogen/generated/android/videotrim+autolinking.cmake +0 -78
- package/nitrogen/generated/android/videotrim+autolinking.gradle +0 -27
- package/nitrogen/generated/android/videotrimOnLoad.cpp +0 -50
- package/nitrogen/generated/android/videotrimOnLoad.hpp +0 -25
- package/nitrogen/generated/ios/VideoTrim+autolinking.rb +0 -60
- package/nitrogen/generated/ios/VideoTrim-Swift-Cxx-Bridge.cpp +0 -96
- package/nitrogen/generated/ios/VideoTrim-Swift-Cxx-Bridge.hpp +0 -374
- package/nitrogen/generated/ios/VideoTrim-Swift-Cxx-Umbrella.hpp +0 -56
- package/nitrogen/generated/ios/VideoTrimAutolinking.mm +0 -33
- package/nitrogen/generated/ios/VideoTrimAutolinking.swift +0 -25
- package/nitrogen/generated/ios/c++/HybridVideoTrimSpecSwift.cpp +0 -11
- package/nitrogen/generated/ios/c++/HybridVideoTrimSpecSwift.hpp +0 -127
- package/nitrogen/generated/ios/swift/EditorConfig.swift +0 -541
- package/nitrogen/generated/ios/swift/FileValidationResult.swift +0 -57
- package/nitrogen/generated/ios/swift/Func_void.swift +0 -46
- package/nitrogen/generated/ios/swift/Func_void_FileValidationResult.swift +0 -46
- package/nitrogen/generated/ios/swift/Func_void_bool.swift +0 -46
- package/nitrogen/generated/ios/swift/Func_void_double.swift +0 -46
- package/nitrogen/generated/ios/swift/Func_void_std__exception_ptr.swift +0 -46
- package/nitrogen/generated/ios/swift/Func_void_std__string.swift +0 -46
- package/nitrogen/generated/ios/swift/Func_void_std__string_std__unordered_map_std__string__std__string_.swift +0 -54
- package/nitrogen/generated/ios/swift/Func_void_std__vector_std__string_.swift +0 -46
- package/nitrogen/generated/ios/swift/HybridVideoTrimSpec.swift +0 -54
- package/nitrogen/generated/ios/swift/HybridVideoTrimSpec_cxx.swift +0 -241
- package/nitrogen/generated/ios/swift/TrimOptions.swift +0 -189
- package/nitrogen/generated/shared/c++/EditorConfig.hpp +0 -253
- package/nitrogen/generated/shared/c++/FileValidationResult.hpp +0 -77
- package/nitrogen/generated/shared/c++/HybridVideoTrimSpec.cpp +0 -27
- package/nitrogen/generated/shared/c++/HybridVideoTrimSpec.hpp +0 -80
- package/nitrogen/generated/shared/c++/TrimOptions.hpp +0 -125
- package/src/VideoTrim.nitro.ts +0 -263
package/ios/VideoTrim.mm
ADDED
|
@@ -0,0 +1,663 @@
|
|
|
1
|
+
#import "VideoTrim.h"
|
|
2
|
+
#import "ProgressAlertController.h"
|
|
3
|
+
#import "VideoTrimmerViewController.h"
|
|
4
|
+
#import "AssetLoader.h"
|
|
5
|
+
#import <React/RCTBridgeModule.h>
|
|
6
|
+
#import <React/RCTUtils.h>
|
|
7
|
+
#import <React/RCTConvert.h>
|
|
8
|
+
#import <AVFoundation/AVFoundation.h>
|
|
9
|
+
#import <Photos/Photos.h>
|
|
10
|
+
#import <UIKit/UIKit.h>
|
|
11
|
+
#import <ffmpegkit/FFmpegKit.h>
|
|
12
|
+
#import <ffmpegkit/FFmpegKitConfig.h>
|
|
13
|
+
|
|
14
|
+
@implementation VideoTrim {
|
|
15
|
+
std::optional<JS::NativeVideoTrim::EditorConfig> _editorConfig;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
RCT_EXPORT_MODULE()
|
|
19
|
+
|
|
20
|
+
- (instancetype)init {
|
|
21
|
+
if (self = [super init]) {
|
|
22
|
+
self.FILE_PREFIX = @"trimmedVideo";
|
|
23
|
+
self.BEFORE_TRIM_PREFIX = @"beforeTrim";
|
|
24
|
+
self.isShowing = NO;
|
|
25
|
+
self.vc = nil;
|
|
26
|
+
self.outputFile = nil;
|
|
27
|
+
self.isVideoType = YES;
|
|
28
|
+
|
|
29
|
+
}
|
|
30
|
+
return self;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Add custom getter and setter
|
|
34
|
+
- (JS::NativeVideoTrim::EditorConfig)editorConfig {
|
|
35
|
+
if (_editorConfig.has_value()) {
|
|
36
|
+
return _editorConfig.value();
|
|
37
|
+
}
|
|
38
|
+
// This shouldn't happen if properly initialized
|
|
39
|
+
@throw [NSException exceptionWithName:@"EditorConfigNotInitialized"
|
|
40
|
+
reason:@"EditorConfig accessed before initialization"
|
|
41
|
+
userInfo:nil];
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
- (void)setEditorConfig:(JS::NativeVideoTrim::EditorConfig)config {
|
|
45
|
+
_editorConfig = config;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
- (void)cleanFiles:(nonnull RCTPromiseResolveBlock)resolve reject:(nonnull RCTPromiseRejectBlock)reject {
|
|
49
|
+
NSURL *documentsDirectory = [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] firstObject];
|
|
50
|
+
NSError *error = nil;
|
|
51
|
+
NSArray *directoryContents = [[NSFileManager defaultManager] contentsOfDirectoryAtURL:documentsDirectory includingPropertiesForKeys:nil options:0 error:&error];
|
|
52
|
+
int successCount = 0;
|
|
53
|
+
for (NSURL *fileURL in directoryContents) {
|
|
54
|
+
NSString *last = [fileURL lastPathComponent];
|
|
55
|
+
if ([last hasPrefix:self.FILE_PREFIX] || [last hasPrefix:self.BEFORE_TRIM_PREFIX]) {
|
|
56
|
+
NSError *removeError = nil;
|
|
57
|
+
[[NSFileManager defaultManager] removeItemAtURL:fileURL error:&removeError];
|
|
58
|
+
if (!removeError) {
|
|
59
|
+
successCount++;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
resolve(@(successCount));
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
- (void)closeEditor {
|
|
67
|
+
if (!self.vc) return;
|
|
68
|
+
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
|
|
69
|
+
[self.vc dismissViewControllerAnimated:YES completion:^{
|
|
70
|
+
[self emitOnHide];
|
|
71
|
+
self.isShowing = NO;
|
|
72
|
+
}];
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
- (void)deleteFile:(nonnull NSString *)filePath resolve:(nonnull RCTPromiseResolveBlock)resolve reject:(nonnull RCTPromiseRejectBlock)reject {
|
|
77
|
+
NSURL *url = [NSURL URLWithString:filePath];
|
|
78
|
+
NSError *error = nil;
|
|
79
|
+
if ([[NSFileManager defaultManager] fileExistsAtPath:[url path]]) {
|
|
80
|
+
[[NSFileManager defaultManager] removeItemAtURL:url error:&error];
|
|
81
|
+
if (error) {
|
|
82
|
+
NSLog(@"[deleteFile] Error: %@", error);
|
|
83
|
+
reject(@"delete_file_error", @"Failed to delete file", error);
|
|
84
|
+
resolve(@(NO));
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
resolve(@(YES));
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
resolve(@(NO));
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
- (void)isValidFile:(nonnull NSString *)url resolve:(nonnull RCTPromiseResolveBlock)resolve reject:(nonnull RCTPromiseRejectBlock)reject {
|
|
94
|
+
NSURL *fileURL = [NSURL URLWithString:url];
|
|
95
|
+
AVAsset *asset = [AVAsset assetWithURL:fileURL];
|
|
96
|
+
NSArray *videoTracks = [asset tracksWithMediaType:AVMediaTypeVideo];
|
|
97
|
+
NSArray *audioTracks = [asset tracksWithMediaType:AVMediaTypeAudio];
|
|
98
|
+
BOOL isValid = (videoTracks.count > 0 || audioTracks.count > 0);
|
|
99
|
+
NSString *fileType = videoTracks.count > 0 ? @"video" : (audioTracks.count > 0 ? @"audio" : @"unknown");
|
|
100
|
+
double durationMs = CMTimeGetSeconds(asset.duration) * 1000;
|
|
101
|
+
NSDictionary *result = @{ @"isValid": @(isValid), @"fileType": fileType, @"duration": @(isValid ? durationMs : -1) };
|
|
102
|
+
resolve(result);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
- (void)listFiles:(nonnull RCTPromiseResolveBlock)resolve reject:(nonnull RCTPromiseRejectBlock)reject {
|
|
106
|
+
NSMutableArray *files = [NSMutableArray array];
|
|
107
|
+
NSURL *documentsDirectory = [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] firstObject];
|
|
108
|
+
NSError *error = nil;
|
|
109
|
+
NSArray *directoryContents = [[NSFileManager defaultManager] contentsOfDirectoryAtURL:documentsDirectory includingPropertiesForKeys:nil options:0 error:&error];
|
|
110
|
+
if (error) {
|
|
111
|
+
NSLog(@"[listFiles] Error: %@", error);
|
|
112
|
+
reject(@"list_files_error", @"Failed to list files", error);
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
for (NSURL *fileURL in directoryContents) {
|
|
116
|
+
NSString *last = [fileURL lastPathComponent];
|
|
117
|
+
if ([last hasPrefix:self.FILE_PREFIX] || [last hasPrefix:self.BEFORE_TRIM_PREFIX]) {
|
|
118
|
+
[files addObject:[fileURL absoluteString]];
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
resolve(files);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
- (void)showEditor:(nonnull NSString *)filePath config:(JS::NativeVideoTrim::EditorConfig &)config {
|
|
125
|
+
if (self.isShowing) return;
|
|
126
|
+
|
|
127
|
+
self.editorConfig = config;
|
|
128
|
+
self.isVideoType = [config.type() isEqualToString:@"video"];
|
|
129
|
+
|
|
130
|
+
NSURL *destPath = nil;
|
|
131
|
+
|
|
132
|
+
if ([filePath hasPrefix:@"http://"] || [filePath hasPrefix:@"https://"]) {
|
|
133
|
+
destPath = [NSURL URLWithString:filePath];
|
|
134
|
+
} else {
|
|
135
|
+
NSLog(@"before rename");
|
|
136
|
+
destPath = [self renameFileAtURL:[NSURL URLWithString:filePath] newName:self.BEFORE_TRIM_PREFIX];
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (!destPath) {
|
|
140
|
+
[self onError:@"Fail to rename file" code:@"INVALID_FILE_PATH"];
|
|
141
|
+
self.isShowing = NO;
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
NSLog(@"✅ destPath created: %@", destPath);
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
dispatch_async(dispatch_get_main_queue(), ^{
|
|
149
|
+
self.vc = [[VideoTrimmerViewController alloc] init];
|
|
150
|
+
if (!self.vc) return;
|
|
151
|
+
|
|
152
|
+
[self.vc configureWithConfig:self.editorConfig];
|
|
153
|
+
|
|
154
|
+
__weak __typeof__(self) weakSelf = self;
|
|
155
|
+
|
|
156
|
+
self.vc.cancelBtnClicked = ^{
|
|
157
|
+
if (!weakSelf.editorConfig.enableCancelDialog()) {
|
|
158
|
+
[weakSelf emitOnCancel];
|
|
159
|
+
[weakSelf.vc dismissViewControllerAnimated:YES completion:^{
|
|
160
|
+
[weakSelf emitOnHide];
|
|
161
|
+
weakSelf.isShowing = NO;
|
|
162
|
+
}];
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
UIAlertController *dialogMessage = [UIAlertController alertControllerWithTitle:weakSelf.editorConfig.cancelDialogTitle()
|
|
167
|
+
message:weakSelf.editorConfig.cancelDialogMessage()
|
|
168
|
+
preferredStyle:UIAlertControllerStyleAlert];
|
|
169
|
+
dialogMessage.overrideUserInterfaceStyle = UIUserInterfaceStyleDark;
|
|
170
|
+
|
|
171
|
+
UIAlertAction *ok = [UIAlertAction actionWithTitle:weakSelf.editorConfig.cancelDialogConfirmText()
|
|
172
|
+
style:UIAlertActionStyleDestructive
|
|
173
|
+
handler:^(UIAlertAction *action) {
|
|
174
|
+
[weakSelf emitOnCancel];
|
|
175
|
+
[weakSelf.vc dismissViewControllerAnimated:YES completion:^{
|
|
176
|
+
[weakSelf emitOnHide];
|
|
177
|
+
weakSelf.isShowing = NO;
|
|
178
|
+
}];
|
|
179
|
+
}];
|
|
180
|
+
|
|
181
|
+
UIAlertAction *cancel = [UIAlertAction actionWithTitle:weakSelf.editorConfig.cancelDialogCancelText()
|
|
182
|
+
style:UIAlertActionStyleCancel
|
|
183
|
+
handler:nil];
|
|
184
|
+
|
|
185
|
+
[dialogMessage addAction:ok];
|
|
186
|
+
[dialogMessage addAction:cancel];
|
|
187
|
+
|
|
188
|
+
UIViewController *root = RCTPresentedViewController();
|
|
189
|
+
if (root) {
|
|
190
|
+
[root presentViewController:dialogMessage animated:YES completion:nil];
|
|
191
|
+
}
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
self.vc.saveBtnClicked = ^(CMTimeRange selectedRange) {
|
|
195
|
+
if (!weakSelf.editorConfig.enableSaveDialog()) {
|
|
196
|
+
[weakSelf trimWithViewController:weakSelf.vc
|
|
197
|
+
inputFile:destPath
|
|
198
|
+
videoDuration:CMTimeGetSeconds(weakSelf.vc.asset.duration)
|
|
199
|
+
startTime:CMTimeGetSeconds(selectedRange.start)
|
|
200
|
+
endTime:CMTimeGetSeconds(CMTimeRangeGetEnd(selectedRange))];
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
UIAlertController *dialogMessage = [UIAlertController alertControllerWithTitle:weakSelf.editorConfig.saveDialogTitle()
|
|
205
|
+
message:weakSelf.editorConfig.saveDialogMessage()
|
|
206
|
+
preferredStyle:UIAlertControllerStyleAlert];
|
|
207
|
+
dialogMessage.overrideUserInterfaceStyle = UIUserInterfaceStyleDark;
|
|
208
|
+
|
|
209
|
+
UIAlertAction *ok = [UIAlertAction actionWithTitle:weakSelf.editorConfig.saveDialogConfirmText()
|
|
210
|
+
style:UIAlertActionStyleDefault
|
|
211
|
+
handler:^(UIAlertAction *action) {
|
|
212
|
+
[weakSelf trimWithViewController:weakSelf.vc
|
|
213
|
+
inputFile:destPath
|
|
214
|
+
videoDuration:CMTimeGetSeconds(weakSelf.vc.asset.duration)
|
|
215
|
+
startTime:CMTimeGetSeconds(selectedRange.start)
|
|
216
|
+
endTime:CMTimeGetSeconds(CMTimeRangeGetEnd(selectedRange))];
|
|
217
|
+
}];
|
|
218
|
+
|
|
219
|
+
UIAlertAction *cancel = [UIAlertAction actionWithTitle:weakSelf.editorConfig.saveDialogCancelText()
|
|
220
|
+
style:UIAlertActionStyleCancel
|
|
221
|
+
handler:nil];
|
|
222
|
+
|
|
223
|
+
[dialogMessage addAction:ok];
|
|
224
|
+
[dialogMessage addAction:cancel];
|
|
225
|
+
|
|
226
|
+
UIViewController *root = RCTPresentedViewController();
|
|
227
|
+
if (root) {
|
|
228
|
+
[root presentViewController:dialogMessage animated:YES completion:nil];
|
|
229
|
+
}
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
self.vc.modalInPresentation = YES;
|
|
233
|
+
|
|
234
|
+
if (self.editorConfig.fullScreenModalIOS()) {
|
|
235
|
+
self.vc.modalPresentationStyle = UIModalPresentationFullScreen;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
UIViewController *root = RCTPresentedViewController();
|
|
239
|
+
if (root) {
|
|
240
|
+
[root presentViewController:self.vc animated:YES completion:^{
|
|
241
|
+
[self emitOnShow];
|
|
242
|
+
self.isShowing = YES;
|
|
243
|
+
|
|
244
|
+
AssetLoader *assetLoader = [[AssetLoader alloc] init];
|
|
245
|
+
assetLoader.delegate = self;
|
|
246
|
+
NSLog(@"🔄 Starting to load asset from: %@", destPath);
|
|
247
|
+
[assetLoader loadAssetWithURL:destPath isVideoType:self.isVideoType];
|
|
248
|
+
}];
|
|
249
|
+
}
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
- (void)trim:(nonnull NSString *)url options:(JS::NativeVideoTrim::TrimOptions &)options resolve:(nonnull RCTPromiseResolveBlock)resolve reject:(nonnull RCTPromiseRejectBlock)reject {
|
|
254
|
+
NSURL *inputURL = [NSURL URLWithString:url];
|
|
255
|
+
if (!inputURL) {
|
|
256
|
+
reject(@"INVALID_URL", @"Invalid input URL", nil);
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// Create output file
|
|
261
|
+
NSTimeInterval timestamp = [[NSDate date] timeIntervalSince1970];
|
|
262
|
+
NSString *outputName = [NSString stringWithFormat:@"%@_%d.%@", self.FILE_PREFIX, (int)timestamp, options.outputExt()];
|
|
263
|
+
NSURL *documentsDirectory = [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] firstObject];
|
|
264
|
+
NSURL *outputURL = [documentsDirectory URLByAppendingPathComponent:outputName];
|
|
265
|
+
|
|
266
|
+
// Prepare FFmpeg command
|
|
267
|
+
NSMutableArray *cmds = [NSMutableArray arrayWithObjects:
|
|
268
|
+
@"-ss", [NSString stringWithFormat:@"%.3f", options.startTime() / 1000.0],
|
|
269
|
+
@"-to", [NSString stringWithFormat:@"%.3f", options.endTime() / 1000.0],
|
|
270
|
+
@"-i", inputURL.absoluteString,
|
|
271
|
+
@"-c", @"copy",
|
|
272
|
+
outputURL.absoluteString, nil];
|
|
273
|
+
|
|
274
|
+
[FFmpegKit executeWithArgumentsAsync:cmds
|
|
275
|
+
withCompleteCallback:^(FFmpegSession* session) {
|
|
276
|
+
SessionState state = [session getState];
|
|
277
|
+
ReturnCode *returnCode = [session getReturnCode];
|
|
278
|
+
|
|
279
|
+
if ([ReturnCode isSuccess:returnCode]) {
|
|
280
|
+
NSDictionary *result = @{
|
|
281
|
+
@"outputPath": outputURL.absoluteString,
|
|
282
|
+
@"startTime": [NSNumber numberWithDouble:options.startTime()],
|
|
283
|
+
@"endTime": [NSNumber numberWithDouble:options.endTime()]
|
|
284
|
+
};
|
|
285
|
+
resolve(result);
|
|
286
|
+
} else {
|
|
287
|
+
NSString *errorMessage = [NSString stringWithFormat:@"Trimming failed with state %@ and rc %@",
|
|
288
|
+
[FFmpegKitConfig sessionStateToString:state],
|
|
289
|
+
returnCode];
|
|
290
|
+
reject(@"TRIMMING_FAILED", errorMessage, nil);
|
|
291
|
+
}
|
|
292
|
+
} withLogCallback:nil withStatisticsCallback:nil];
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
- (void)trimWithViewController:(VideoTrimmerViewController *)viewController
|
|
296
|
+
inputFile:(NSURL *)inputFile
|
|
297
|
+
videoDuration:(double)videoDuration
|
|
298
|
+
startTime:(double)startTime
|
|
299
|
+
endTime:(double)endTime {
|
|
300
|
+
[self.vc pausePlayer];
|
|
301
|
+
|
|
302
|
+
NSTimeInterval timestamp = [[NSDate date] timeIntervalSince1970];
|
|
303
|
+
NSString *outputName = [NSString stringWithFormat:@"%@_%d.%@", self.FILE_PREFIX, (int)timestamp, self.editorConfig.outputExt()];
|
|
304
|
+
NSURL *documentsDirectory = [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] firstObject];
|
|
305
|
+
self.outputFile = [documentsDirectory URLByAppendingPathComponent:outputName];
|
|
306
|
+
|
|
307
|
+
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
|
|
308
|
+
formatter.dateFormat = @"yyyy-MM-dd'T'HH:mm:ss.SSSSSSZ";
|
|
309
|
+
formatter.timeZone = [NSTimeZone timeZoneWithAbbreviation:@"UTC"];
|
|
310
|
+
NSString *dateTime = [formatter stringFromDate:[NSDate date]];
|
|
311
|
+
|
|
312
|
+
[self emitOnStartTrimming];
|
|
313
|
+
|
|
314
|
+
__block FFmpegSession *ffmpegSession = nil;
|
|
315
|
+
ProgressAlertController *progressAlert = [[ProgressAlertController alloc] init];
|
|
316
|
+
progressAlert.modalPresentationStyle = UIModalPresentationOverFullScreen;
|
|
317
|
+
progressAlert.modalTransitionStyle = UIModalTransitionStyleCrossDissolve;
|
|
318
|
+
[progressAlert setTitle:self.editorConfig.trimmingText()];
|
|
319
|
+
|
|
320
|
+
if (self.editorConfig.enableCancelTrimming()) {
|
|
321
|
+
[progressAlert setCancelTitle:self.editorConfig.cancelTrimmingButtonText()];
|
|
322
|
+
[progressAlert showCancelBtn];
|
|
323
|
+
__weak __typeof__(progressAlert) weakProgressAlert = progressAlert;
|
|
324
|
+
|
|
325
|
+
progressAlert.onDismiss = ^{
|
|
326
|
+
if (self.editorConfig.enableCancelTrimmingDialog()) {
|
|
327
|
+
UIAlertController *dialogMessage = [UIAlertController alertControllerWithTitle:self.editorConfig.cancelTrimmingDialogTitle()
|
|
328
|
+
message:self.editorConfig.cancelTrimmingDialogMessage()
|
|
329
|
+
preferredStyle:UIAlertControllerStyleAlert];
|
|
330
|
+
dialogMessage.overrideUserInterfaceStyle = UIUserInterfaceStyleDark;
|
|
331
|
+
|
|
332
|
+
UIAlertAction *ok = [UIAlertAction actionWithTitle:self.editorConfig.cancelTrimmingDialogConfirmText()
|
|
333
|
+
style:UIAlertActionStyleDestructive
|
|
334
|
+
handler:^(UIAlertAction *action) {
|
|
335
|
+
if (ffmpegSession) {
|
|
336
|
+
[ffmpegSession cancel];
|
|
337
|
+
} else {
|
|
338
|
+
[self emitOnCancelTrimming];
|
|
339
|
+
}
|
|
340
|
+
[weakProgressAlert dismissViewControllerAnimated:YES completion:nil];
|
|
341
|
+
}];
|
|
342
|
+
|
|
343
|
+
UIAlertAction *cancel = [UIAlertAction actionWithTitle:self.editorConfig.cancelTrimmingDialogCancelText()
|
|
344
|
+
style:UIAlertActionStyleCancel
|
|
345
|
+
handler:nil];
|
|
346
|
+
|
|
347
|
+
[dialogMessage addAction:ok];
|
|
348
|
+
[dialogMessage addAction:cancel];
|
|
349
|
+
|
|
350
|
+
UIViewController *root = RCTPresentedViewController();
|
|
351
|
+
if (root) {
|
|
352
|
+
[root presentViewController:dialogMessage animated:YES completion:nil];
|
|
353
|
+
}
|
|
354
|
+
} else {
|
|
355
|
+
if (ffmpegSession) {
|
|
356
|
+
[ffmpegSession cancel];
|
|
357
|
+
} else {
|
|
358
|
+
[self emitOnCancelTrimming];
|
|
359
|
+
}
|
|
360
|
+
[weakProgressAlert dismissViewControllerAnimated:YES completion:nil];
|
|
361
|
+
}
|
|
362
|
+
};
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
UIViewController *root = RCTPresentedViewController();
|
|
366
|
+
if (root) {
|
|
367
|
+
[root presentViewController:progressAlert animated:YES completion:nil];
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
NSMutableArray *cmds = [NSMutableArray arrayWithObjects:
|
|
371
|
+
@"-ss", [NSString stringWithFormat:@"%.0fms", startTime * 1000],
|
|
372
|
+
@"-to", [NSString stringWithFormat:@"%.0fms", endTime * 1000], nil];
|
|
373
|
+
|
|
374
|
+
if (self.editorConfig.enableRotation()) {
|
|
375
|
+
[cmds addObjectsFromArray:@[
|
|
376
|
+
@"-display_rotation", [NSString stringWithFormat:@"%.0f", self.editorConfig.rotationAngle()]
|
|
377
|
+
]];
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
[cmds addObjectsFromArray:@[
|
|
381
|
+
@"-i", inputFile.absoluteString,
|
|
382
|
+
@"-c", @"copy",
|
|
383
|
+
@"-metadata", [NSString stringWithFormat:@"creation_time=%@", dateTime],
|
|
384
|
+
self.outputFile.absoluteString
|
|
385
|
+
]];
|
|
386
|
+
|
|
387
|
+
NSLog(@"Command: %@", [cmds componentsJoinedByString:@" "]);
|
|
388
|
+
|
|
389
|
+
NSDictionary *eventPayload = @{ @"command": [cmds componentsJoinedByString:@" "] };
|
|
390
|
+
|
|
391
|
+
[self emitOnLog:eventPayload];
|
|
392
|
+
|
|
393
|
+
ffmpegSession = [FFmpegKit executeWithArgumentsAsync:cmds
|
|
394
|
+
withCompleteCallback:^(FFmpegSession* session) {
|
|
395
|
+
dispatch_async(dispatch_get_main_queue(), ^{
|
|
396
|
+
[progressAlert dismissViewControllerAnimated:YES completion:^{
|
|
397
|
+
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
|
398
|
+
SessionState state = [session getState];
|
|
399
|
+
ReturnCode *returnCode = [session getReturnCode];
|
|
400
|
+
|
|
401
|
+
if ([ReturnCode isSuccess:returnCode]) {
|
|
402
|
+
NSDictionary *eventPayload = @{
|
|
403
|
+
@"outputPath": self.outputFile.absoluteString,
|
|
404
|
+
@"startTime": [NSString stringWithFormat:@"%.0f", startTime * 1000],
|
|
405
|
+
@"endTime": [NSString stringWithFormat:@"%.0f", endTime * 1000],
|
|
406
|
+
@"duration": [NSString stringWithFormat:@"%.0f", videoDuration * 1000]
|
|
407
|
+
};
|
|
408
|
+
[self emitOnFinishTrimming:eventPayload];
|
|
409
|
+
|
|
410
|
+
if (self.editorConfig.saveToPhoto() && self.isVideoType) {
|
|
411
|
+
[PHPhotoLibrary requestAuthorization:^(PHAuthorizationStatus status) {
|
|
412
|
+
if (status != PHAuthorizationStatusAuthorized) {
|
|
413
|
+
[self onError:@"Permission to access Photo Library is not granted" code:@"NO_PHOTO_PERMISSION"];
|
|
414
|
+
return;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
[[PHPhotoLibrary sharedPhotoLibrary] performChanges:^{
|
|
418
|
+
PHAssetChangeRequest *request = [PHAssetChangeRequest creationRequestForAssetFromVideoAtFileURL:self.outputFile];
|
|
419
|
+
request.creationDate = [NSDate date];
|
|
420
|
+
} completionHandler:^(BOOL success, NSError *error) {
|
|
421
|
+
if (success) {
|
|
422
|
+
NSLog(@"Edited video saved to Photo Library successfully.");
|
|
423
|
+
if (self.editorConfig.removeAfterSavedToPhoto()) {
|
|
424
|
+
[self deleteFileAtURL:self.outputFile];
|
|
425
|
+
}
|
|
426
|
+
} else {
|
|
427
|
+
[self onError:[NSString stringWithFormat:@"Failed to save edited video to Photo Library: %@", error.localizedDescription ?: @"Unknown error"] code:@"FAIL_TO_SAVE_TO_PHOTO"];
|
|
428
|
+
if (self.editorConfig.removeAfterFailedToSavePhoto()) {
|
|
429
|
+
[self deleteFileAtURL:self.outputFile];
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
}];
|
|
433
|
+
}];
|
|
434
|
+
} else if (self.editorConfig.openDocumentsOnFinish()) {
|
|
435
|
+
[self saveFileToFilesApp:self.outputFile];
|
|
436
|
+
return;
|
|
437
|
+
} else if (self.editorConfig.openShareSheetOnFinish()) {
|
|
438
|
+
[self shareFile:self.outputFile];
|
|
439
|
+
return;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
if (self.editorConfig.closeWhenFinish()) {
|
|
443
|
+
[self closeEditor];
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
} else if ([ReturnCode isCancel:returnCode]) {
|
|
447
|
+
[self emitOnCancelTrimming];
|
|
448
|
+
} else {
|
|
449
|
+
NSString *errorMessage = [NSString stringWithFormat:@"Command failed with state %@ and rc %@.%@",
|
|
450
|
+
[FFmpegKitConfig sessionStateToString:state],
|
|
451
|
+
returnCode,
|
|
452
|
+
[session getFailStackTrace] ?: @""];
|
|
453
|
+
[self onError:errorMessage code:@"TRIMMING_FAILED"];
|
|
454
|
+
if (self.editorConfig.closeWhenFinish()) {
|
|
455
|
+
[self closeEditor];
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
});
|
|
459
|
+
}];
|
|
460
|
+
});
|
|
461
|
+
} withLogCallback:^(Log* log) {
|
|
462
|
+
NSLog(@"FFmpeg process started with log %@", [log getMessage]);
|
|
463
|
+
NSDictionary *eventPayload = @{
|
|
464
|
+
@"level": @([log getLevel]),
|
|
465
|
+
@"message": [log getMessage] ?: @"",
|
|
466
|
+
@"sessionId": @([log getSessionId])
|
|
467
|
+
};
|
|
468
|
+
[self emitOnLog:eventPayload];
|
|
469
|
+
} withStatisticsCallback:^(Statistics* statistics) {
|
|
470
|
+
int timeInMilliseconds = [statistics getTime];
|
|
471
|
+
if (timeInMilliseconds > 0) {
|
|
472
|
+
double completePercentage = timeInMilliseconds / (videoDuration * 1000);
|
|
473
|
+
dispatch_async(dispatch_get_main_queue(), ^{
|
|
474
|
+
[progressAlert setProgress:(float)completePercentage];
|
|
475
|
+
});
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
NSDictionary *eventPayload = @{
|
|
479
|
+
@"sessionId": @([statistics getSessionId]),
|
|
480
|
+
@"videoFrameNumber": @([statistics getVideoFrameNumber]),
|
|
481
|
+
@"videoFps": @([statistics getVideoFps]),
|
|
482
|
+
@"videoQuality": @([statistics getVideoQuality]),
|
|
483
|
+
@"size": @([statistics getSize]),
|
|
484
|
+
@"time": @([statistics getTime]),
|
|
485
|
+
@"bitrate": @([statistics getBitrate]),
|
|
486
|
+
@"speed": @([statistics getSpeed])
|
|
487
|
+
};
|
|
488
|
+
[self emitOnStatistics:eventPayload];
|
|
489
|
+
}];
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
- (void)saveFileToFilesApp:(NSURL *)fileURL {
|
|
493
|
+
dispatch_async(dispatch_get_main_queue(), ^{
|
|
494
|
+
UIDocumentPickerViewController *documentPicker = [[UIDocumentPickerViewController alloc] initWithURL:fileURL inMode:UIDocumentPickerModeExportToService];
|
|
495
|
+
documentPicker.delegate = self;
|
|
496
|
+
documentPicker.modalPresentationStyle = UIModalPresentationFormSheet;
|
|
497
|
+
|
|
498
|
+
UIViewController *root = RCTPresentedViewController();
|
|
499
|
+
if (root) {
|
|
500
|
+
[root presentViewController:documentPicker animated:YES completion:nil];
|
|
501
|
+
}
|
|
502
|
+
});
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
- (void)shareFile:(NSURL *)fileURL {
|
|
506
|
+
dispatch_async(dispatch_get_main_queue(), ^{
|
|
507
|
+
UIActivityViewController *activityViewController = [[UIActivityViewController alloc] initWithActivityItems:@[fileURL] applicationActivities:nil];
|
|
508
|
+
|
|
509
|
+
activityViewController.completionWithItemsHandler = ^(UIActivityType activityType, BOOL completed, NSArray *returnedItems, NSError *error) {
|
|
510
|
+
if (error) {
|
|
511
|
+
NSString *message = [NSString stringWithFormat:@"Sharing error: %@", error.localizedDescription];
|
|
512
|
+
NSLog(@"%@", message);
|
|
513
|
+
[self onError:message code:@"FAIL_TO_SHARE"];
|
|
514
|
+
|
|
515
|
+
if (self.editorConfig.removeAfterFailedToShare()) {
|
|
516
|
+
[self deleteFileAtURL:fileURL];
|
|
517
|
+
}
|
|
518
|
+
return;
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
if (completed) {
|
|
522
|
+
NSLog(@"User completed the sharing activity");
|
|
523
|
+
if (self.editorConfig.removeAfterShared()) {
|
|
524
|
+
[self deleteFileAtURL:fileURL];
|
|
525
|
+
}
|
|
526
|
+
} else {
|
|
527
|
+
NSLog(@"User cancelled or failed to complete the sharing activity");
|
|
528
|
+
if (self.editorConfig.removeAfterFailedToShare()) {
|
|
529
|
+
[self deleteFileAtURL:fileURL];
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
[self closeEditor];
|
|
534
|
+
};
|
|
535
|
+
|
|
536
|
+
UIViewController *root = RCTPresentedViewController();
|
|
537
|
+
if (root) {
|
|
538
|
+
[root presentViewController:activityViewController animated:YES completion:nil];
|
|
539
|
+
}
|
|
540
|
+
});
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
- (int)deleteFileAtURL:(NSURL *)url {
|
|
544
|
+
NSError *error = nil;
|
|
545
|
+
if ([[NSFileManager defaultManager] fileExistsAtPath:[url path]]) {
|
|
546
|
+
[[NSFileManager defaultManager] removeItemAtURL:url error:&error];
|
|
547
|
+
if (error) {
|
|
548
|
+
NSLog(@"[deleteFile] Error deleting files: %@", error);
|
|
549
|
+
return 2;
|
|
550
|
+
}
|
|
551
|
+
return 0;
|
|
552
|
+
}
|
|
553
|
+
return 1;
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
- (NSURL *)renameFileAtURL:(NSURL *)url newName:(NSString *)newName {
|
|
557
|
+
NSString *fileExtension = url.pathExtension;
|
|
558
|
+
NSTimeInterval timestamp = [[NSDate date] timeIntervalSince1970];
|
|
559
|
+
NSString *newFileName = [NSString stringWithFormat:@"%@_%lld.%@", newName, (long long)timestamp, fileExtension];
|
|
560
|
+
|
|
561
|
+
NSURL *documentsDirectory = [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory
|
|
562
|
+
inDomains:NSUserDomainMask] firstObject];
|
|
563
|
+
NSURL *newURL = [documentsDirectory URLByAppendingPathComponent:newFileName];
|
|
564
|
+
|
|
565
|
+
NSError *error;
|
|
566
|
+
BOOL success = [[NSFileManager defaultManager] copyItemAtURL:url toURL:newURL error:&error];
|
|
567
|
+
|
|
568
|
+
if (success) {
|
|
569
|
+
return newURL;
|
|
570
|
+
} else {
|
|
571
|
+
NSLog(@"Failed to rename file: %@", error.localizedDescription);
|
|
572
|
+
return nil;
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
- (void)onError:(NSString *)message code:(NSString *)code {
|
|
577
|
+
NSDictionary *eventPayload = @{
|
|
578
|
+
@"message": message,
|
|
579
|
+
@"errorCode": code
|
|
580
|
+
};
|
|
581
|
+
[self emitOnError:eventPayload];
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
#pragma mark - AssetLoaderDelegate
|
|
585
|
+
|
|
586
|
+
- (void)assetLoaderDidSucceed:(AssetLoader *)assetLoader {
|
|
587
|
+
NSLog(@"assetLoaderDidSucceed");
|
|
588
|
+
dispatch_async(dispatch_get_main_queue(), ^{
|
|
589
|
+
if (self.vc) {
|
|
590
|
+
self.vc.asset = assetLoader.asset;
|
|
591
|
+
|
|
592
|
+
NSDictionary *eventPayload = @{
|
|
593
|
+
@"duration": [NSNumber numberWithFloat:CMTimeGetSeconds(self.vc.asset.duration) * 1000]
|
|
594
|
+
};
|
|
595
|
+
|
|
596
|
+
[self emitOnLoad:eventPayload];
|
|
597
|
+
}
|
|
598
|
+
});
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
- (void)assetLoader:(AssetLoader *)assetLoader didFailWithError:(NSError *)error forKey:(NSString *)key {
|
|
602
|
+
NSLog(@"❌ Asset loading failed: %@ for key: %@", error.localizedDescription, key);
|
|
603
|
+
|
|
604
|
+
NSString *message = [NSString stringWithFormat:@"Failed to load %@: %@", key, error.localizedDescription];
|
|
605
|
+
|
|
606
|
+
[self onError:message code:@"FAIL_TO_LOAD_MEDIA"];
|
|
607
|
+
|
|
608
|
+
if (self.vc) {
|
|
609
|
+
[self.vc onAssetFailToLoad];
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
if (self.editorConfig.alertOnFailToLoad()) {
|
|
613
|
+
dispatch_async(dispatch_get_main_queue(), ^{
|
|
614
|
+
UIAlertController *dialogMessage = [UIAlertController alertControllerWithTitle:self.editorConfig.alertOnFailTitle()
|
|
615
|
+
message:self.editorConfig.alertOnFailMessage()
|
|
616
|
+
preferredStyle:UIAlertControllerStyleAlert];
|
|
617
|
+
dialogMessage.overrideUserInterfaceStyle = UIUserInterfaceStyleDark;
|
|
618
|
+
|
|
619
|
+
UIAlertAction *ok = [UIAlertAction actionWithTitle:self.editorConfig.alertOnFailCloseText()
|
|
620
|
+
style:UIAlertActionStyleDefault
|
|
621
|
+
handler:nil];
|
|
622
|
+
|
|
623
|
+
[dialogMessage addAction:ok];
|
|
624
|
+
|
|
625
|
+
UIViewController *root = RCTPresentedViewController();
|
|
626
|
+
if (root) {
|
|
627
|
+
[root presentViewController:dialogMessage animated:YES completion:nil];
|
|
628
|
+
}
|
|
629
|
+
});
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
#pragma mark - UIDocumentPickerDelegate
|
|
634
|
+
|
|
635
|
+
- (void)documentPicker:(UIDocumentPickerViewController *)controller didPickDocumentsAtURLs:(NSArray<NSURL *> *)urls {
|
|
636
|
+
// Handle document picker results if needed
|
|
637
|
+
NSLog(@"Document picker selected URLs: %@", urls);
|
|
638
|
+
|
|
639
|
+
if (self.editorConfig.removeAfterSavedToDocuments()) {
|
|
640
|
+
[self deleteFileAtURL:self.outputFile];
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
[self closeEditor];
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
- (void)documentPickerWasCancelled:(UIDocumentPickerViewController *)controller {
|
|
647
|
+
// Handle document picker cancellation if needed
|
|
648
|
+
NSLog(@"Document picker was cancelled");
|
|
649
|
+
|
|
650
|
+
if (self.editorConfig.removeAfterFailedToSaveDocuments()) {
|
|
651
|
+
[self deleteFileAtURL:self.outputFile];
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
[self closeEditor];
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
#pragma mark - TurboModule
|
|
658
|
+
|
|
659
|
+
- (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:(const facebook::react::ObjCTurboModule::InitParams &)params {
|
|
660
|
+
return std::make_shared<facebook::react::NativeVideoTrimSpecJSI>(params);
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
@end
|