react-native-video-trim 1.0.20 → 1.0.22
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/ios/VideoTrim.swift +7 -1
- package/ios/VideoTrimmer.swift +387 -330
- package/ios/VideoTrimmerThumb.swift +139 -90
- package/ios/VideoTrimmerViewController.swift +224 -166
- package/package.json +1 -1
|
@@ -1,41 +1,23 @@
|
|
|
1
|
-
//
|
|
2
|
-
// VideoTrimmerViewController.swift
|
|
3
|
-
// react-native-video-trim
|
|
4
|
-
//
|
|
5
|
-
// Created by Duc Trung Mai on 17/1/24.
|
|
6
|
-
//
|
|
7
|
-
|
|
8
1
|
import UIKit
|
|
9
2
|
import AVKit
|
|
10
3
|
|
|
11
4
|
extension CMTime {
|
|
12
5
|
var displayString: String {
|
|
13
6
|
let offset = TimeInterval(seconds)
|
|
14
|
-
let numberOfNanosecondsFloat = (offset - TimeInterval(Int(offset))) *
|
|
7
|
+
let numberOfNanosecondsFloat = (offset - TimeInterval(Int(offset))) * 100.0
|
|
15
8
|
let nanoseconds = Int(numberOfNanosecondsFloat)
|
|
9
|
+
|
|
10
|
+
let formatter = CMTime.dateFormatter
|
|
11
|
+
return String(format: "%@.%02d", formatter.string(from: offset) ?? "00:00", nanoseconds)
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
private static var dateFormatter: DateComponentsFormatter = {
|
|
16
15
|
let formatter = DateComponentsFormatter()
|
|
17
16
|
formatter.unitsStyle = .positional
|
|
18
17
|
formatter.zeroFormattingBehavior = .pad
|
|
19
18
|
formatter.allowedUnits = [.minute, .second]
|
|
20
|
-
return
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
extension AVAsset {
|
|
25
|
-
var fullRange: CMTimeRange {
|
|
26
|
-
return CMTimeRange(start: .zero, duration: duration)
|
|
27
|
-
}
|
|
28
|
-
func trimmedComposition(_ range: CMTimeRange) -> AVAsset {
|
|
29
|
-
guard CMTimeRangeEqual(fullRange, range) == false else {return self}
|
|
30
|
-
|
|
31
|
-
let composition = AVMutableComposition()
|
|
32
|
-
try? composition.insertTimeRange(range, of: self, at: .zero)
|
|
33
|
-
|
|
34
|
-
if let videoTrack = tracks(withMediaType: .video).first {
|
|
35
|
-
composition.tracks.forEach {$0.preferredTransform = videoTrack.preferredTransform}
|
|
36
|
-
}
|
|
37
|
-
return composition
|
|
38
|
-
}
|
|
19
|
+
return formatter
|
|
20
|
+
}()
|
|
39
21
|
}
|
|
40
22
|
|
|
41
23
|
@available(iOS 13.0, *)
|
|
@@ -48,69 +30,61 @@ class VideoTrimmerViewController: UIViewController {
|
|
|
48
30
|
var cancelBtnClicked: (() -> Void)?
|
|
49
31
|
var saveBtnClicked: ((CMTimeRange) -> Void)?
|
|
50
32
|
|
|
51
|
-
let playerController = AVPlayerViewController()
|
|
52
|
-
var trimmer: VideoTrimmer!
|
|
53
|
-
var timingStackView: UIStackView!
|
|
54
|
-
var leadingTrimLabel: UILabel!
|
|
55
|
-
var currentTimeLabel: UILabel!
|
|
56
|
-
var trailingTrimLabel: UILabel!
|
|
57
|
-
|
|
33
|
+
private let playerController = AVPlayerViewController()
|
|
34
|
+
private var trimmer: VideoTrimmer!
|
|
35
|
+
private var timingStackView: UIStackView!
|
|
36
|
+
private var leadingTrimLabel: UILabel!
|
|
37
|
+
private var currentTimeLabel: UILabel!
|
|
38
|
+
private var trailingTrimLabel: UILabel!
|
|
58
39
|
private var btnStackView: UIStackView!
|
|
59
40
|
private var cancelBtn: UIButton!
|
|
60
41
|
private var playBtn: UIButton!
|
|
61
42
|
private var saveBtn: UIButton!
|
|
62
43
|
private let playIcon = UIImage(systemName: "play.fill")
|
|
63
44
|
private let pauseIcon = UIImage(systemName: "pause.fill")
|
|
64
|
-
|
|
65
|
-
private var wasPlaying = false
|
|
66
|
-
private var player: AVPlayer! {playerController.player}
|
|
45
|
+
private var player: AVPlayer! { playerController.player }
|
|
67
46
|
private var timeObserverToken: Any?
|
|
68
47
|
|
|
48
|
+
var isSeekInProgress: Bool = false // Marker
|
|
49
|
+
private var chaseTime = CMTime.zero
|
|
50
|
+
private var preferredFrameRate: Float = 23.98
|
|
51
|
+
|
|
69
52
|
|
|
70
53
|
// MARK: - Input
|
|
71
|
-
@objc private func
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
wasPlaying = (player.timeControlStatus != .paused)
|
|
75
|
-
player.pause()
|
|
76
|
-
|
|
77
|
-
updatePlayerAsset()
|
|
54
|
+
@objc private func didBeginTrimmingFromStart(_ sender: VideoTrimmer) {
|
|
55
|
+
handleBeforeProgressChange()
|
|
78
56
|
}
|
|
79
57
|
|
|
80
|
-
@objc private func
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
if wasPlaying == true {
|
|
84
|
-
player.play()
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
updatePlayerAsset()
|
|
58
|
+
@objc private func leadingGrabberChanged(_ sender: VideoTrimmer) {
|
|
59
|
+
handleProgressChanged(time: trimmer.selectedRange.start)
|
|
88
60
|
}
|
|
89
61
|
|
|
90
|
-
@objc private func
|
|
91
|
-
|
|
62
|
+
@objc private func didEndTrimmingFromStart(_ sender: VideoTrimmer) {
|
|
63
|
+
handleTrimmingEnd(true)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
@objc private func didBeginTrimmingFromEnd(_ sender: VideoTrimmer) {
|
|
67
|
+
handleBeforeProgressChange()
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
@objc private func trailingGrabberChanged(_ sender: VideoTrimmer) {
|
|
71
|
+
handleProgressChanged(time: trimmer.selectedRange.end)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
@objc private func didEndTrimmingFromEnd(_ sender: VideoTrimmer) {
|
|
75
|
+
handleTrimmingEnd(false)
|
|
92
76
|
}
|
|
93
77
|
|
|
94
78
|
@objc private func didBeginScrubbing(_ sender: VideoTrimmer) {
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
wasPlaying = (player.timeControlStatus != .paused)
|
|
98
|
-
player.pause()
|
|
79
|
+
handleBeforeProgressChange()
|
|
99
80
|
}
|
|
100
81
|
|
|
101
82
|
@objc private func didEndScrubbing(_ sender: VideoTrimmer) {
|
|
102
83
|
updateLabels()
|
|
103
|
-
|
|
104
|
-
if wasPlaying == true {
|
|
105
|
-
player.play()
|
|
106
|
-
}
|
|
107
84
|
}
|
|
108
85
|
|
|
109
86
|
@objc private func progressDidChanged(_ sender: VideoTrimmer) {
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
let time = CMTimeSubtract(trimmer.progress, trimmer.selectedRange.start)
|
|
113
|
-
player.seek(to: time, toleranceBefore: .zero, toleranceAfter: .zero)
|
|
87
|
+
handleProgressChanged(time: trimmer.progress)
|
|
114
88
|
}
|
|
115
89
|
|
|
116
90
|
// MARK: - Private
|
|
@@ -120,38 +94,81 @@ class VideoTrimmerViewController: UIViewController {
|
|
|
120
94
|
trailingTrimLabel.text = trimmer.selectedRange.end.displayString
|
|
121
95
|
}
|
|
122
96
|
|
|
123
|
-
private func
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
97
|
+
private func handleBeforeProgressChange() {
|
|
98
|
+
updateLabels()
|
|
99
|
+
player.pause()
|
|
100
|
+
setPlayBtnIcon()
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
private func handleProgressChanged(time: CMTime) {
|
|
104
|
+
updateLabels()
|
|
105
|
+
seek(to: time)
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
private func handleTrimmingEnd(_ start: Bool) {
|
|
109
|
+
self.trimmer.progress = start ? trimmer.selectedRange.start : trimmer.selectedRange.end
|
|
110
|
+
updateLabels()
|
|
111
|
+
player.seek(to: trimmer.progress, toleranceBefore: .zero, toleranceAfter: .zero)
|
|
129
112
|
}
|
|
130
113
|
|
|
131
114
|
// MARK: - UIViewController
|
|
132
115
|
override func viewDidLoad() {
|
|
133
116
|
super.viewDidLoad()
|
|
134
117
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
cancelBtn.setTitleColor(.white, for: .normal)
|
|
142
|
-
cancelBtn.addTarget(self, action: #selector(onCancelBtnClicked), for: .touchUpInside)
|
|
118
|
+
setupView()
|
|
119
|
+
setupButtons()
|
|
120
|
+
setupTimeLabels()
|
|
121
|
+
setupVideoTrimmer()
|
|
122
|
+
setupPlayerController()
|
|
123
|
+
setupTimeObserver()
|
|
143
124
|
|
|
125
|
+
updateLabels()
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
override func viewWillDisappear(_ animated: Bool) {
|
|
129
|
+
super.viewWillDisappear(animated)
|
|
144
130
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
131
|
+
player.pause()
|
|
132
|
+
if let token = timeObserverToken {
|
|
133
|
+
player.removeTimeObserver(token)
|
|
134
|
+
timeObserverToken = nil
|
|
135
|
+
}
|
|
136
|
+
playerController.player = nil
|
|
137
|
+
playerController.dismiss(animated: false, completion: nil)
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
@objc private func togglePlay(sender: UIButton) {
|
|
141
|
+
if player.timeControlStatus == .playing {
|
|
142
|
+
player.pause()
|
|
143
|
+
} else {
|
|
144
|
+
if CMTimeCompare(trimmer.progress, trimmer.selectedRange.end) != -1 {
|
|
145
|
+
trimmer.progress = trimmer.selectedRange.start
|
|
146
|
+
player.seek(to: trimmer.progress, toleranceBefore: .zero, toleranceAfter: .zero)
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
player.play()
|
|
150
|
+
}
|
|
149
151
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
152
|
+
setPlayBtnIcon()
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
@objc private func onSaveBtnClicked() {
|
|
156
|
+
saveBtnClicked?(trimmer.selectedRange)
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
@objc private func onCancelBtnClicked() {
|
|
160
|
+
cancelBtnClicked?()
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// MARK: - Setup Methods
|
|
164
|
+
private func setupView() {
|
|
165
|
+
view.backgroundColor = .black
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
private func setupButtons() {
|
|
169
|
+
cancelBtn = UIButton.createButton(title: cancelBtnText, font: .systemFont(ofSize: 18), titleColor: .white, target: self, action: #selector(onCancelBtnClicked))
|
|
170
|
+
playBtn = UIButton.createButton(image: playIcon, tintColor: .systemBlue, target: self, action: #selector(togglePlay(sender:)))
|
|
171
|
+
saveBtn = UIButton.createButton(title: saveButtonText, font: .systemFont(ofSize: 18), titleColor: .systemBlue, target: self, action: #selector(onSaveBtnClicked))
|
|
155
172
|
|
|
156
173
|
btnStackView = UIStackView(arrangedSubviews: [cancelBtn, playBtn, saveBtn])
|
|
157
174
|
btnStackView.axis = .horizontal
|
|
@@ -163,24 +180,14 @@ class VideoTrimmerViewController: UIViewController {
|
|
|
163
180
|
NSLayoutConstraint.activate([
|
|
164
181
|
btnStackView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 16),
|
|
165
182
|
btnStackView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -16),
|
|
166
|
-
btnStackView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -16)
|
|
183
|
+
btnStackView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -16)
|
|
167
184
|
])
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
leadingTrimLabel
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
currentTimeLabel = UILabel()
|
|
176
|
-
currentTimeLabel.font = UIFont.preferredFont(forTextStyle: .caption1)
|
|
177
|
-
currentTimeLabel.textAlignment = .center
|
|
178
|
-
currentTimeLabel.textColor = .white
|
|
179
|
-
|
|
180
|
-
trailingTrimLabel = UILabel()
|
|
181
|
-
trailingTrimLabel.font = UIFont.preferredFont(forTextStyle: .caption1)
|
|
182
|
-
trailingTrimLabel.textAlignment = .right
|
|
183
|
-
trailingTrimLabel.textColor = .white
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
private func setupTimeLabels() {
|
|
188
|
+
leadingTrimLabel = UILabel.createLabel(textAlignment: .left, textColor: .white)
|
|
189
|
+
currentTimeLabel = UILabel.createLabel(textAlignment: .center, textColor: .white)
|
|
190
|
+
trailingTrimLabel = UILabel.createLabel(textAlignment: .right, textColor: .white)
|
|
184
191
|
|
|
185
192
|
timingStackView = UIStackView(arrangedSubviews: [leadingTrimLabel, currentTimeLabel, trailingTrimLabel])
|
|
186
193
|
timingStackView.axis = .horizontal
|
|
@@ -192,50 +199,57 @@ class VideoTrimmerViewController: UIViewController {
|
|
|
192
199
|
NSLayoutConstraint.activate([
|
|
193
200
|
timingStackView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 16),
|
|
194
201
|
timingStackView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -16),
|
|
195
|
-
timingStackView.bottomAnchor.constraint(equalTo: btnStackView.topAnchor, constant: -8)
|
|
202
|
+
timingStackView.bottomAnchor.constraint(equalTo: btnStackView.topAnchor, constant: -8)
|
|
196
203
|
])
|
|
197
|
-
|
|
198
|
-
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
private func setupVideoTrimmer() {
|
|
199
207
|
trimmer = VideoTrimmer()
|
|
200
|
-
trimmer.asset = asset
|
|
208
|
+
trimmer.asset = asset
|
|
201
209
|
trimmer.minimumDuration = CMTime(seconds: 1, preferredTimescale: 600)
|
|
202
210
|
|
|
203
|
-
if
|
|
204
|
-
trimmer.maximumDuration = CMTime(seconds: max(1, Double(
|
|
205
|
-
|
|
206
|
-
// guard check to make sure max duration can only <= asset.duration
|
|
207
|
-
if CMTimeCompare(trimmer.maximumDuration, asset.duration) == 1 {
|
|
211
|
+
if let maxDuration = maximumDuration {
|
|
212
|
+
trimmer.maximumDuration = CMTime(seconds: max(1, Double(maxDuration)), preferredTimescale: 600)
|
|
213
|
+
if trimmer.maximumDuration > asset.duration {
|
|
208
214
|
trimmer.maximumDuration = asset.duration
|
|
209
215
|
}
|
|
210
|
-
|
|
211
216
|
trimmer.selectedRange = CMTimeRange(start: .zero, end: trimmer.maximumDuration)
|
|
212
217
|
}
|
|
213
|
-
|
|
214
|
-
if
|
|
215
|
-
trimmer.minimumDuration = CMTime(seconds: max(1, Double(
|
|
218
|
+
|
|
219
|
+
if let minDuration = minimumDuration {
|
|
220
|
+
trimmer.minimumDuration = CMTime(seconds: max(1, Double(minDuration)), preferredTimescale: 600)
|
|
216
221
|
}
|
|
217
222
|
|
|
218
|
-
trimmer.addTarget(self, action: #selector(didBeginTrimming(_:)), for: VideoTrimmer.didBeginTrimming)
|
|
219
|
-
trimmer.addTarget(self, action: #selector(didEndTrimming(_:)), for: VideoTrimmer.didEndTrimming)
|
|
220
|
-
trimmer.addTarget(self, action: #selector(selectedRangeDidChanged(_:)), for: VideoTrimmer.selectedRangeChanged)
|
|
221
223
|
trimmer.addTarget(self, action: #selector(didBeginScrubbing(_:)), for: VideoTrimmer.didBeginScrubbing)
|
|
222
224
|
trimmer.addTarget(self, action: #selector(didEndScrubbing(_:)), for: VideoTrimmer.didEndScrubbing)
|
|
223
225
|
trimmer.addTarget(self, action: #selector(progressDidChanged(_:)), for: VideoTrimmer.progressChanged)
|
|
226
|
+
|
|
227
|
+
trimmer.addTarget(self, action: #selector(didBeginTrimmingFromStart(_:)), for: VideoTrimmer.didBeginTrimmingFromStart)
|
|
228
|
+
trimmer.addTarget(self, action: #selector(leadingGrabberChanged(_:)), for: VideoTrimmer.leadingGrabberChanged)
|
|
229
|
+
trimmer.addTarget(self, action: #selector(didEndTrimmingFromStart(_:)), for: VideoTrimmer.didEndTrimmingFromStart)
|
|
230
|
+
|
|
231
|
+
trimmer.addTarget(self, action: #selector(didBeginTrimmingFromEnd(_:)), for: VideoTrimmer.didBeginTrimmingFromEnd)
|
|
232
|
+
trimmer.addTarget(self, action: #selector(trailingGrabberChanged(_:)), for: VideoTrimmer.trailingGrabberChanged)
|
|
233
|
+
trimmer.addTarget(self, action: #selector(didEndTrimmingFromEnd(_:)), for: VideoTrimmer.didEndTrimmingFromEnd)
|
|
224
234
|
view.addSubview(trimmer)
|
|
225
235
|
trimmer.translatesAutoresizingMaskIntoConstraints = false
|
|
226
236
|
NSLayoutConstraint.activate([
|
|
227
237
|
trimmer.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor),
|
|
228
238
|
trimmer.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor),
|
|
229
239
|
trimmer.bottomAnchor.constraint(equalTo: timingStackView.topAnchor, constant: -16),
|
|
230
|
-
trimmer.heightAnchor.constraint(equalToConstant: 50)
|
|
240
|
+
trimmer.heightAnchor.constraint(equalToConstant: 50)
|
|
231
241
|
])
|
|
232
|
-
|
|
233
|
-
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
private func setupPlayerController() {
|
|
245
|
+
playerController.showsPlaybackControls = false
|
|
234
246
|
if #available(iOS 16.0, *) {
|
|
235
|
-
playerController.allowsVideoFrameAnalysis = false
|
|
247
|
+
playerController.allowsVideoFrameAnalysis = false
|
|
236
248
|
}
|
|
237
249
|
playerController.player = AVPlayer()
|
|
238
|
-
|
|
250
|
+
player.replaceCurrentItem(with: AVPlayerItem(asset: asset))
|
|
251
|
+
|
|
252
|
+
try? AVAudioSession.sharedInstance().setCategory(.playback, mode: .default, options: [])
|
|
239
253
|
addChild(playerController)
|
|
240
254
|
view.addSubview(playerController.view)
|
|
241
255
|
playerController.view.translatesAutoresizingMaskIntoConstraints = false
|
|
@@ -245,56 +259,100 @@ class VideoTrimmerViewController: UIViewController {
|
|
|
245
259
|
playerController.view.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
|
|
246
260
|
playerController.view.bottomAnchor.constraint(equalTo: trimmer.topAnchor, constant: -16)
|
|
247
261
|
])
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
private func setupTimeObserver() {
|
|
251
265
|
timeObserverToken = player.addPeriodicTimeObserver(forInterval: CMTime(value: 1, timescale: 30), queue: .main) { [weak self] time in
|
|
252
|
-
guard let self = self else {return}
|
|
253
|
-
|
|
254
|
-
currentTimeLabel.text = player.currentTime().displayString
|
|
266
|
+
guard let self = self else { return }
|
|
255
267
|
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
let finalTime = self.trimmer.trimmingState == .none ? CMTimeAdd(time, self.trimmer.selectedRange.start) : time
|
|
260
|
-
self.trimmer.progress = finalTime
|
|
268
|
+
if self.player.timeControlStatus != .playing {
|
|
269
|
+
return
|
|
270
|
+
}
|
|
261
271
|
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
272
|
+
self.trimmer.progress = time
|
|
273
|
+
|
|
274
|
+
// pause if reach end of selected range
|
|
275
|
+
if CMTimeCompare(self.trimmer.progress, trimmer.selectedRange.end) == 1 {
|
|
276
|
+
player.pause()
|
|
277
|
+
self.trimmer.progress = trimmer.selectedRange.end
|
|
278
|
+
player.seek(to: trimmer.selectedRange.end, toleranceBefore: .zero, toleranceAfter: .zero)
|
|
266
279
|
}
|
|
280
|
+
|
|
281
|
+
currentTimeLabel.text = trimmer.progress.displayString
|
|
282
|
+
|
|
283
|
+
self.setPlayBtnIcon()
|
|
267
284
|
}
|
|
268
|
-
|
|
269
|
-
updateLabels()
|
|
270
285
|
}
|
|
271
286
|
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
player.pause()
|
|
276
|
-
if timeObserverToken != nil {
|
|
277
|
-
player.removeTimeObserver(timeObserverToken as Any)
|
|
278
|
-
timeObserverToken = nil
|
|
279
|
-
}
|
|
280
|
-
playerController.player = nil
|
|
281
|
-
playerController.dismiss(animated: false, completion: nil)
|
|
287
|
+
private func setPlayBtnIcon() {
|
|
288
|
+
self.playBtn.setImage(self.player.timeControlStatus == .playing ? self.pauseIcon : self.playIcon, for: .normal)
|
|
282
289
|
}
|
|
283
290
|
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
291
|
+
// ====Smoother seek
|
|
292
|
+
public func seek(to time: CMTime) {
|
|
293
|
+
seekSmoothlyToTime(newChaseTime: time)
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
private func seekSmoothlyToTime(newChaseTime: CMTime) {
|
|
297
|
+
if CMTimeCompare(newChaseTime, chaseTime) != 0 {
|
|
298
|
+
chaseTime = newChaseTime
|
|
299
|
+
|
|
300
|
+
if !isSeekInProgress {
|
|
301
|
+
trySeekToChaseTime()
|
|
302
|
+
}
|
|
289
303
|
}
|
|
290
|
-
|
|
291
304
|
}
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
305
|
+
|
|
306
|
+
private func trySeekToChaseTime() {
|
|
307
|
+
guard player?.status == .readyToPlay else { return }
|
|
308
|
+
actuallySeekToTime()
|
|
295
309
|
}
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
310
|
+
|
|
311
|
+
private func actuallySeekToTime() {
|
|
312
|
+
isSeekInProgress = true
|
|
313
|
+
let seekTimeInProgress = chaseTime
|
|
314
|
+
|
|
315
|
+
player?.seek(to: seekTimeInProgress, toleranceBefore: .zero, toleranceAfter: .zero) { [weak self] _ in
|
|
316
|
+
guard let `self` = self else { return }
|
|
317
|
+
|
|
318
|
+
if CMTimeCompare(seekTimeInProgress, self.chaseTime) == 0 {
|
|
319
|
+
self.isSeekInProgress = false
|
|
320
|
+
} else {
|
|
321
|
+
self.trySeekToChaseTime()
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
private extension UIButton {
|
|
328
|
+
static func createButton(title: String? = nil, image: UIImage? = nil, font: UIFont? = nil, titleColor: UIColor? = nil, tintColor: UIColor? = nil, target: Any?, action: Selector) -> UIButton {
|
|
329
|
+
let button = UIButton(type: .system)
|
|
330
|
+
if let title = title {
|
|
331
|
+
button.setTitle(title, for: .normal)
|
|
332
|
+
}
|
|
333
|
+
if let image = image {
|
|
334
|
+
button.setImage(image, for: .normal)
|
|
335
|
+
}
|
|
336
|
+
if let font = font {
|
|
337
|
+
button.titleLabel?.font = font
|
|
338
|
+
}
|
|
339
|
+
if let titleColor = titleColor {
|
|
340
|
+
button.setTitleColor(titleColor, for: .normal)
|
|
341
|
+
}
|
|
342
|
+
if let tintColor = tintColor {
|
|
343
|
+
button.tintColor = tintColor
|
|
344
|
+
}
|
|
345
|
+
button.addTarget(target, action: action, for: .touchUpInside)
|
|
346
|
+
return button
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
private extension UILabel {
|
|
351
|
+
static func createLabel(textAlignment: NSTextAlignment, textColor: UIColor) -> UILabel {
|
|
352
|
+
let label = UILabel()
|
|
353
|
+
label.font = UIFont.preferredFont(forTextStyle: .caption1)
|
|
354
|
+
label.textAlignment = textAlignment
|
|
355
|
+
label.textColor = textColor
|
|
356
|
+
return label
|
|
299
357
|
}
|
|
300
358
|
}
|