react-native-video-trim 5.0.5 → 5.1.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.
- package/README.md +6 -2
- package/VideoTrim.podspec +1 -1
- package/ios/AssetLoader.swift +99 -0
- package/ios/ErrorCode.swift +17 -0
- package/ios/ProgressAlertController.swift +100 -0
- package/ios/VideoTrim-Bridging-Header.h +1 -0
- package/ios/VideoTrim.h +2 -28
- package/ios/VideoTrim.mm +156 -639
- package/ios/VideoTrim.swift +938 -0
- package/ios/VideoTrimProtocol.swift +10 -0
- package/ios/VideoTrimmer.swift +872 -0
- package/ios/VideoTrimmerThumb.swift +175 -0
- package/ios/VideoTrimmerViewController.swift +578 -0
- package/package.json +1 -1
- package/ios/AssetLoader.h +0 -19
- package/ios/AssetLoader.mm +0 -87
- package/ios/ErrorCode.h +0 -9
- package/ios/ProgressAlertController.h +0 -12
- package/ios/ProgressAlertController.mm +0 -106
- package/ios/VideoTrimmer.h +0 -67
- package/ios/VideoTrimmer.mm +0 -863
- package/ios/VideoTrimmerThumb.h +0 -23
- package/ios/VideoTrimmerThumb.mm +0 -175
- package/ios/VideoTrimmerViewController.h +0 -52
- package/ios/VideoTrimmerViewController.mm +0 -533
|
@@ -1,533 +0,0 @@
|
|
|
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
|