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.
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 +22 -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 +13 -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 +26 -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
@@ -1,557 +0,0 @@
1
- //
2
- // VideoTrimmerViewController.swift
3
- // VideoTrim
4
- //
5
- // Created by Duc Trung Mai on 20/5/25.
6
- //
7
-
8
- import UIKit
9
- import AVKit
10
-
11
- extension CMTime {
12
- var displayString: String {
13
- let offset = TimeInterval(seconds)
14
- let numberOfNanosecondsFloat = (offset - TimeInterval(Int(offset))) * 100.0
15
- let nanoseconds = Int(numberOfNanosecondsFloat)
16
-
17
- let formatter = CMTime.dateFormatter
18
- return String(format: "%@.%02d", formatter.string(from: offset) ?? "00:00", nanoseconds)
19
- }
20
-
21
- private static var dateFormatter: DateComponentsFormatter = {
22
- let formatter = DateComponentsFormatter()
23
- formatter.unitsStyle = .positional
24
- formatter.zeroFormattingBehavior = .pad
25
- formatter.allowedUnits = [.minute, .second]
26
- return formatter
27
- }()
28
- }
29
-
30
- @available(iOS 13.0, *)
31
- class VideoTrimmerViewController: UIViewController {
32
- var asset: AVAsset? {
33
- didSet {
34
- if let _ = asset {
35
- setupVideoTrimmer()
36
- setupPlayerController()
37
- setupTimeObserver()
38
- updateLabels()
39
- }
40
- }
41
- }
42
- private var maximumDuration: Int?
43
- private var minimumDuration: Int?
44
- private var cancelButtonText = "Cancel"
45
- private var saveButtonText = "Save"
46
- var cancelBtnClicked: (() -> Void)?
47
- var saveBtnClicked: ((CMTimeRange) -> Void)?
48
- private var enableHapticFeedback = true
49
-
50
- private let playerController = AVPlayerViewController()
51
- private var trimmer: VideoTrimmer!
52
- private var timingStackView: UIStackView!
53
- private var leadingTrimLabel: UILabel!
54
- private var currentTimeLabel: UILabel!
55
- private var trailingTrimLabel: UILabel!
56
- private var btnStackView: UIStackView!
57
- private var cancelBtn: UIButton!
58
- private var playBtn: UIButton!
59
- private let loadingIndicator = UIActivityIndicatorView()
60
- private var saveBtn: UIButton!
61
- private let playIcon = UIImage(systemName: "play.fill")
62
- private let pauseIcon = UIImage(systemName: "pause.fill")
63
- private let audioBannerView = UIImage(systemName: "airpodsmax")
64
- private var player: AVPlayer! { playerController.player }
65
- private var timeObserverToken: Any?
66
- private var autoplay = false
67
- private var jumpToPositionOnLoad: Double = 0;
68
- private var headerText: String?
69
- private var headerTextSize = 16
70
- private var headerTextColor: Double?
71
- private var headerView: UIView?
72
-
73
- var isSeekInProgress: Bool = false // Marker
74
- private var chaseTime = CMTime.zero
75
- private var preferredFrameRate: Float = 23.98
76
-
77
- public func onAssetFailToLoad() {
78
- loadingIndicator.stopAnimating()
79
- btnStackView.removeArrangedSubview(loadingIndicator)
80
- loadingIndicator.removeFromSuperview()
81
-
82
- let imageViewContainer = UIView()
83
- let imageView = UIImageView(image: UIImage(systemName: "exclamationmark.triangle.fill"))
84
- imageView.tintColor = .systemYellow
85
- imageView.translatesAutoresizingMaskIntoConstraints = false
86
-
87
- imageViewContainer.addSubview(imageView)
88
- NSLayoutConstraint.activate([
89
- imageView.widthAnchor.constraint(equalToConstant: 36),
90
- imageView.heightAnchor.constraint(equalToConstant: 36),
91
- imageView.centerXAnchor.constraint(equalTo: imageViewContainer.centerXAnchor),
92
- imageView.centerYAnchor.constraint(equalTo: imageViewContainer.centerYAnchor)
93
- ])
94
- imageViewContainer.alpha = 0
95
-
96
- btnStackView.insertArrangedSubview(imageViewContainer, at: 1)
97
-
98
- UIView.animate(withDuration: 0.25, animations: {
99
- imageViewContainer.alpha = 1
100
- })
101
- }
102
-
103
- // MARK: - Input
104
- @objc private func didBeginTrimmingFromStart(_ sender: VideoTrimmer) {
105
- handleBeforeProgressChange()
106
- }
107
-
108
- @objc private func leadingGrabberChanged(_ sender: VideoTrimmer) {
109
- handleProgressChanged(time: trimmer.selectedRange.start)
110
- }
111
-
112
- @objc private func didEndTrimmingFromStart(_ sender: VideoTrimmer) {
113
- handleTrimmingEnd(true)
114
- }
115
-
116
- @objc private func didBeginTrimmingFromEnd(_ sender: VideoTrimmer) {
117
- handleBeforeProgressChange()
118
- }
119
-
120
- @objc private func trailingGrabberChanged(_ sender: VideoTrimmer) {
121
- handleProgressChanged(time: trimmer.selectedRange.end)
122
- }
123
-
124
- @objc private func didEndTrimmingFromEnd(_ sender: VideoTrimmer) {
125
- handleTrimmingEnd(false)
126
- }
127
-
128
- @objc private func didBeginScrubbing(_ sender: VideoTrimmer) {
129
- handleBeforeProgressChange()
130
- }
131
-
132
- @objc private func didEndScrubbing(_ sender: VideoTrimmer) {
133
- updateLabels()
134
- }
135
-
136
- @objc private func progressDidChanged(_ sender: VideoTrimmer) {
137
- handleProgressChanged(time: trimmer.progress)
138
- }
139
-
140
- // MARK: - Private
141
- private func updateLabels() {
142
- leadingTrimLabel.text = trimmer.selectedRange.start.displayString
143
- currentTimeLabel.text = trimmer.progress.displayString
144
- trailingTrimLabel.text = trimmer.selectedRange.end.displayString
145
- }
146
-
147
- private func handleBeforeProgressChange() {
148
- updateLabels()
149
- player.pause()
150
- setPlayBtnIcon()
151
- }
152
-
153
- private func handleProgressChanged(time: CMTime) {
154
- updateLabels()
155
- seek(to: time)
156
- }
157
-
158
- private func handleTrimmingEnd(_ start: Bool) {
159
- self.trimmer.progress = start ? trimmer.selectedRange.start : trimmer.selectedRange.end
160
- updateLabels()
161
- seek(to: trimmer.progress)
162
- }
163
-
164
- // MARK: - UIViewController
165
- override func viewDidLoad() {
166
- super.viewDidLoad()
167
-
168
- setupView()
169
- setupButtons()
170
- setupTimeLabels()
171
- }
172
-
173
- override func viewWillDisappear(_ animated: Bool) {
174
- super.viewWillDisappear(animated)
175
-
176
- // if asset has been initialized
177
- guard let _ = asset else { return }
178
- player.pause()
179
-
180
- // Clean up the observer
181
- player.removeObserver(self, forKeyPath: "status")
182
-
183
- if let token = timeObserverToken {
184
- player.removeTimeObserver(token)
185
- timeObserverToken = nil
186
- }
187
- // Remove observer
188
- NotificationCenter.default.removeObserver(self, name: .AVPlayerItemDidPlayToEndTime, object: player.currentItem)
189
-
190
- playerController.player = nil
191
- playerController.dismiss(animated: false, completion: nil)
192
- }
193
-
194
- public func pausePlayer() {
195
- player.pause()
196
- setPlayBtnIcon()
197
- }
198
-
199
- @objc private func togglePlay(sender: UIButton) {
200
- if player.timeControlStatus == .playing {
201
- player.pause()
202
- } else {
203
- if CMTimeCompare(trimmer.progress, trimmer.selectedRange.end) != -1 {
204
- trimmer.progress = trimmer.selectedRange.start
205
- self.seek(to: trimmer.progress)
206
- }
207
-
208
- player.play()
209
- }
210
-
211
- setPlayBtnIcon()
212
- }
213
-
214
- @objc private func onSaveBtnClicked() {
215
- saveBtnClicked?(trimmer.selectedRange)
216
- }
217
-
218
- @objc private func onCancelBtnClicked() {
219
- cancelBtnClicked?()
220
- }
221
-
222
- // MARK: - Setup Methods
223
- private func setupView() {
224
- self.overrideUserInterfaceStyle = .dark
225
- view.backgroundColor = .black // need to have this otherwise during animation the background of this VC is still white in white theme
226
-
227
- if let headerText = headerText {
228
- headerView = UIView()
229
- headerView!.translatesAutoresizingMaskIntoConstraints = false
230
- view.addSubview(headerView!)
231
- let headerTextView = UITextView()
232
- headerTextView.text = headerText
233
- headerTextView.textAlignment = .center
234
- headerTextView.textColor = UIColor.color(fromHexNumber: headerTextColor as NSNumber?, defaultColor: .white)
235
- headerTextView.font = UIFont.systemFont(ofSize: CGFloat(headerTextSize)) // Set font size here
236
- headerTextView.translatesAutoresizingMaskIntoConstraints = false
237
- headerView!.addSubview(headerTextView)
238
-
239
- NSLayoutConstraint.activate([
240
- // HeaderView constraints
241
- headerView!.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
242
- headerView!.leadingAnchor.constraint(equalTo: view.leadingAnchor),
243
- headerView!.trailingAnchor.constraint(equalTo: view.trailingAnchor),
244
- headerView!.heightAnchor.constraint(greaterThanOrEqualToConstant: 50),
245
-
246
- // HeaderText constraints
247
- headerTextView.topAnchor.constraint(equalTo: headerView!.topAnchor),
248
- headerTextView.bottomAnchor.constraint(equalTo: headerView!.bottomAnchor),
249
- headerTextView.leadingAnchor.constraint(equalTo: headerView!.leadingAnchor),
250
- headerTextView.trailingAnchor.constraint(equalTo: headerView!.trailingAnchor),
251
- ])
252
-
253
- view.layoutIfNeeded() // layout after activate constraints, otherwise headerView height = screen height, which leads to playerViewController is missing at runtime
254
- }
255
- }
256
-
257
- private func setupButtons() {
258
- cancelBtn = UIButton.createButton(title: cancelButtonText, font: .systemFont(ofSize: 18), titleColor: .white, target: self, action: #selector(onCancelBtnClicked))
259
- playBtn = UIButton.createButton(image: playIcon, tintColor: .white, target: self, action: #selector(togglePlay(sender:)))
260
- playBtn.alpha = 0
261
- playBtn.isEnabled = false
262
-
263
- saveBtn = UIButton.createButton(title: saveButtonText, font: .systemFont(ofSize: 18), titleColor: .systemBlue, target: self, action: #selector(onSaveBtnClicked))
264
- saveBtn.alpha = 0
265
- saveBtn.isEnabled = false
266
-
267
- btnStackView = UIStackView(arrangedSubviews: [cancelBtn, loadingIndicator, saveBtn])
268
- btnStackView.axis = .horizontal
269
- btnStackView.alignment = .center
270
- btnStackView.distribution = .fillEqually
271
- btnStackView.spacing = UIStackView.spacingUseSystem
272
- btnStackView.translatesAutoresizingMaskIntoConstraints = false
273
-
274
- view.addSubview(btnStackView)
275
-
276
- NSLayoutConstraint.activate([
277
- btnStackView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 16),
278
- btnStackView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -16),
279
- btnStackView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -16)
280
- ])
281
-
282
- loadingIndicator.startAnimating()
283
- }
284
-
285
- private func setupTimeLabels() {
286
- leadingTrimLabel = UILabel.createLabel(textAlignment: .left, textColor: .white)
287
- leadingTrimLabel.text = "00:00.000"
288
- currentTimeLabel = UILabel.createLabel(textAlignment: .center, textColor: .white)
289
- currentTimeLabel.text = "00:00.000"
290
- trailingTrimLabel = UILabel.createLabel(textAlignment: .right, textColor: .white)
291
- trailingTrimLabel.text = "00:00.000"
292
-
293
- timingStackView = UIStackView(arrangedSubviews: [leadingTrimLabel, currentTimeLabel, trailingTrimLabel])
294
- timingStackView.axis = .horizontal
295
- timingStackView.alignment = .fill
296
- timingStackView.distribution = .fillEqually
297
- timingStackView.spacing = UIStackView.spacingUseSystem
298
- view.addSubview(timingStackView)
299
- timingStackView.translatesAutoresizingMaskIntoConstraints = false
300
- NSLayoutConstraint.activate([
301
- timingStackView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 16),
302
- timingStackView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -16),
303
- timingStackView.bottomAnchor.constraint(equalTo: btnStackView.topAnchor, constant: -8)
304
- ])
305
- }
306
-
307
- private func setupVideoTrimmer() {
308
- trimmer = VideoTrimmer()
309
- trimmer.asset = asset
310
- trimmer.minimumDuration = CMTime(seconds: 1, preferredTimescale: 600)
311
- trimmer.enableHapticFeedback = enableHapticFeedback
312
-
313
- if let maxDuration = maximumDuration {
314
- trimmer.maximumDuration = CMTime(seconds: max(1, Double(maxDuration)), preferredTimescale: 600)
315
- if trimmer.maximumDuration > asset!.duration {
316
- trimmer.maximumDuration = asset!.duration
317
- }
318
- trimmer.selectedRange = CMTimeRange(start: .zero, end: trimmer.maximumDuration)
319
- }
320
-
321
- if let minDuration = minimumDuration {
322
- trimmer.minimumDuration = CMTime(seconds: max(1, Double(minDuration)), preferredTimescale: 600)
323
- }
324
-
325
- trimmer.addTarget(self, action: #selector(didBeginScrubbing(_:)), for: VideoTrimmer.didBeginScrubbing)
326
- trimmer.addTarget(self, action: #selector(didEndScrubbing(_:)), for: VideoTrimmer.didEndScrubbing)
327
- trimmer.addTarget(self, action: #selector(progressDidChanged(_:)), for: VideoTrimmer.progressChanged)
328
-
329
- trimmer.addTarget(self, action: #selector(didBeginTrimmingFromStart(_:)), for: VideoTrimmer.didBeginTrimmingFromStart)
330
- trimmer.addTarget(self, action: #selector(leadingGrabberChanged(_:)), for: VideoTrimmer.leadingGrabberChanged)
331
- trimmer.addTarget(self, action: #selector(didEndTrimmingFromStart(_:)), for: VideoTrimmer.didEndTrimmingFromStart)
332
-
333
- trimmer.addTarget(self, action: #selector(didBeginTrimmingFromEnd(_:)), for: VideoTrimmer.didBeginTrimmingFromEnd)
334
- trimmer.addTarget(self, action: #selector(trailingGrabberChanged(_:)), for: VideoTrimmer.trailingGrabberChanged)
335
- trimmer.addTarget(self, action: #selector(didEndTrimmingFromEnd(_:)), for: VideoTrimmer.didEndTrimmingFromEnd)
336
- trimmer.alpha = 0
337
- view.addSubview(trimmer)
338
- trimmer.translatesAutoresizingMaskIntoConstraints = false
339
- NSLayoutConstraint.activate([
340
- trimmer.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor),
341
- trimmer.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor),
342
- trimmer.bottomAnchor.constraint(equalTo: timingStackView.topAnchor, constant: -16),
343
- trimmer.heightAnchor.constraint(equalToConstant: 50)
344
- ])
345
-
346
- UIView.animate(withDuration: 0.25, animations: {
347
- self.trimmer.alpha = 1
348
- })
349
- }
350
-
351
- private func setupPlayerController() {
352
- playerController.showsPlaybackControls = false
353
- if #available(iOS 16.0, *) {
354
- playerController.allowsVideoFrameAnalysis = false
355
- }
356
- playerController.player = AVPlayer()
357
- player.replaceCurrentItem(with: AVPlayerItem(asset: asset!))
358
-
359
- // Add observer for player status
360
- player.addObserver(self, forKeyPath: "status", options: [.new, .initial], context: nil)
361
-
362
- try? AVAudioSession.sharedInstance().setCategory(.playback, mode: .default, options: [])
363
- addChild(playerController)
364
- view.addSubview(playerController.view)
365
- playerController.view.translatesAutoresizingMaskIntoConstraints = false
366
- NSLayoutConstraint.activate([
367
- playerController.view.leadingAnchor.constraint(equalTo: view.leadingAnchor),
368
- playerController.view.trailingAnchor.constraint(equalTo: view.trailingAnchor),
369
- playerController.view.topAnchor.constraint(equalTo: headerView != nil ? headerView!.bottomAnchor : view.safeAreaLayoutGuide.topAnchor),
370
- playerController.view.bottomAnchor.constraint(equalTo: trimmer.topAnchor, constant: -16)
371
- ])
372
-
373
- // Add observer for the end of playback
374
- NotificationCenter.default.addObserver(self, selector: #selector(playerDidFinishPlaying), name: .AVPlayerItemDidPlayToEndTime, object: player.currentItem)
375
- }
376
-
377
- @objc private func playerDidFinishPlaying(note: NSNotification) {
378
- // Directly set the play icon
379
- // the reason in at this time player.timeControlStatus == .playing still returns true
380
- playBtn.setImage(self.playIcon, for: .normal)
381
- }
382
-
383
- private func setupTimeObserver() {
384
- timeObserverToken = player.addPeriodicTimeObserver(forInterval: CMTime(value: 1, timescale: 30), queue: .main) { [weak self] time in
385
- guard let self = self else { return }
386
-
387
- if self.player.timeControlStatus != .playing {
388
- return
389
- }
390
-
391
- self.trimmer.progress = time
392
-
393
- // pause if reach end of selected range
394
- if CMTimeCompare(self.trimmer.progress, trimmer.selectedRange.end) == 1 {
395
- player.pause()
396
- self.trimmer.progress = trimmer.selectedRange.end
397
- self.seek(to: trimmer.selectedRange.end)
398
- }
399
-
400
- currentTimeLabel.text = trimmer.progress.displayString
401
-
402
- self.setPlayBtnIcon()
403
- }
404
- }
405
-
406
- private func setPlayBtnIcon() {
407
- self.playBtn.setImage(self.player.timeControlStatus == .playing ? self.pauseIcon : self.playIcon, for: .normal)
408
- }
409
-
410
- // ====Smoother seek
411
- public func seek(to time: CMTime) {
412
- seekSmoothlyToTime(newChaseTime: time)
413
- }
414
-
415
- private func seekSmoothlyToTime(newChaseTime: CMTime) {
416
- if CMTimeCompare(newChaseTime, chaseTime) != 0 {
417
- chaseTime = newChaseTime
418
-
419
- if !isSeekInProgress {
420
- trySeekToChaseTime()
421
- }
422
- }
423
- }
424
-
425
- private func trySeekToChaseTime() {
426
- guard player?.status == .readyToPlay else { return }
427
- actuallySeekToTime()
428
- }
429
-
430
- private func actuallySeekToTime() {
431
- isSeekInProgress = true
432
- let seekTimeInProgress = chaseTime
433
-
434
- player?.seek(to: seekTimeInProgress, toleranceBefore: .zero, toleranceAfter: .zero) { [weak self] _ in
435
- guard let `self` = self else { return }
436
-
437
- if CMTimeCompare(seekTimeInProgress, self.chaseTime) == 0 {
438
- self.isSeekInProgress = false
439
- } else {
440
- self.trySeekToChaseTime()
441
- }
442
- }
443
- }
444
-
445
- public func configure(config: EditorConfig) {
446
- if config.maxDuration > 0 {
447
- maximumDuration = Int(config.maxDuration)
448
- }
449
-
450
- if config.minDuration > 0 {
451
- minimumDuration = Int(config.minDuration)
452
- }
453
-
454
- self.cancelButtonText = config.cancelButtonText
455
- self.saveButtonText = config.saveButtonText
456
- self.jumpToPositionOnLoad = config.jumpToPositionOnLoad
457
-
458
-
459
- enableHapticFeedback = config.enableHapticFeedback
460
- autoplay = config.autoplay
461
-
462
- if !config.headerText.isEmpty {
463
- self.headerText = config.headerText
464
-
465
- headerTextSize = Int(config.headerTextSize)
466
- headerTextColor = config.headerTextColor
467
- }
468
- }
469
-
470
- override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
471
- if keyPath == "status" {
472
- if player.status == .readyToPlay {
473
- loadingIndicator.stopAnimating()
474
- btnStackView.removeArrangedSubview(loadingIndicator)
475
- loadingIndicator.removeFromSuperview()
476
- btnStackView.insertArrangedSubview(playBtn, at: 1)
477
-
478
- UIView.animate(withDuration: 0.25, animations: {
479
- self.playBtn.alpha = 1
480
- self.playBtn.isEnabled = true
481
- self.saveBtn.alpha = 1
482
- self.saveBtn.isEnabled = true
483
- })
484
-
485
- if jumpToPositionOnLoad > 0 {
486
- let duration = (asset?.duration.seconds ?? 0) * 1000
487
- let time = jumpToPositionOnLoad > duration ? duration : jumpToPositionOnLoad
488
- let cmtime = CMTime(value: CMTimeValue(time), timescale: 1000)
489
-
490
- self.seek(to: cmtime)
491
- self.trimmer.progress = cmtime
492
- self.currentTimeLabel.text = self.trimmer.progress.displayString
493
- }
494
-
495
- if autoplay {
496
- togglePlay(sender: playBtn)
497
- }
498
- }
499
- }
500
- }
501
- }
502
-
503
- private extension UIButton {
504
- static func createButton(title: String? = nil, image: UIImage? = nil, font: UIFont? = nil, titleColor: UIColor? = nil, tintColor: UIColor? = nil, target: Any?, action: Selector) -> UIButton {
505
- let button = UIButton(type: .system)
506
- if let title = title {
507
- button.setTitle(title, for: .normal)
508
- }
509
- if let image = image {
510
- button.setImage(image, for: .normal)
511
- }
512
- if let font = font {
513
- button.titleLabel?.font = font
514
- }
515
- if let titleColor = titleColor {
516
- button.setTitleColor(titleColor, for: .normal)
517
- }
518
- if let tintColor = tintColor {
519
- button.tintColor = tintColor
520
- }
521
- button.addTarget(target, action: action, for: .touchUpInside)
522
- return button
523
- }
524
- }
525
-
526
- private extension UILabel {
527
- static func createLabel(textAlignment: NSTextAlignment, textColor: UIColor) -> UILabel {
528
- let label = UILabel()
529
- label.font = UIFont.preferredFont(forTextStyle: .caption1)
530
- label.textAlignment = textAlignment
531
- label.textColor = textColor
532
- return label
533
- }
534
- }
535
-
536
- extension UIColor {
537
- static func color(fromHexNumber hex: NSNumber?, defaultColor: UIColor = .black) -> UIColor {
538
- guard let hexValue = hex?.int32Value else {
539
- return defaultColor
540
- }
541
-
542
- // Extract RGB components from the hex value
543
- let red = CGFloat((hexValue >> 16) & 0xFF) / 255.0 // Extract red (bits 16-23)
544
- let green = CGFloat((hexValue >> 8) & 0xFF) / 255.0 // Extract green (bits 8-15)
545
- let blue = CGFloat(hexValue & 0xFF) / 255.0 // Extract blue (bits 0-7)
546
-
547
- // Check if alpha is included (if hex is 0xAARRGGBB)
548
- let alpha: CGFloat
549
- if hexValue > 0xFFFFFF { // If the value is larger than 0xFFFFFF, it includes alpha
550
- alpha = CGFloat((hexValue >> 24) & 0xFF) / 255.0 // Extract alpha (bits 24-31)
551
- } else {
552
- alpha = 1.0 // Default to opaque
553
- }
554
-
555
- return UIColor(red: red, green: green, blue: blue, alpha: alpha)
556
- }
557
- }
@@ -1,4 +0,0 @@
1
- "use strict";
2
-
3
- export {};
4
- //# sourceMappingURL=VideoTrim.nitro.js.map
@@ -1 +0,0 @@
1
- {"version":3,"names":[],"sourceRoot":"../../src","sources":["VideoTrim.nitro.ts"],"mappings":"","ignoreList":[]}