PQVideoCutingOprateView.swift 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300
  1. //
  2. // PQVideoCutingOprateView.swift
  3. // PQSpeed
  4. //
  5. // Created by SanW on 2021/5/9.
  6. // Copyright © 2021 BytesFlow. All rights reserved.
  7. //
  8. import UIKit
  9. import BFCommonKit
  10. class PQVideoCutingOprateView: UIView {
  11. // 距离左边间隔
  12. var leftMargin: CGFloat = 0
  13. // 距离右边间隔
  14. var rightMargin: CGFloat = 0
  15. // 距离上边间隔
  16. var topMargin: CGFloat = 14
  17. // 距离下边间隔
  18. var bottomMargin: CGFloat = 20
  19. /// 上下线条的高度
  20. var lineHeight: CGFloat = 3.0
  21. /// 进度宽度
  22. var progressWidth: CGFloat = 12.0
  23. // 左右裁剪操作宽度
  24. var cutingOprateWidth: CGFloat = 15
  25. // 开始时间 默认 0
  26. private var cutStartTime: CGFloat = 0
  27. // 结束时间
  28. private var cutEndTime: CGFloat = 0
  29. // 视频总时长
  30. private var totalDuration: CGFloat = 0
  31. // 最小裁剪大小 默认 10s
  32. private var cutMinDuration: CGFloat = 10
  33. // 最大裁剪大小 默认 40s
  34. private var cutMaxDuration: CGFloat = 40
  35. /// 每秒宽度
  36. private var perSecondWidth: CGFloat = 0
  37. /// 裁剪总时长
  38. private var cutTotalTime: CGFloat = 0
  39. /// 当前滑动的位置
  40. private var preOriginX: CGFloat = 0
  41. /// 当前滑动的view
  42. private var currentPanView: UIView?
  43. private var progress: CGFloat {
  44. if progressView.frame.minX >= (rightOprateView.frame.minX - (((progressView.frame.width - 3) / 2) + 3)) {
  45. return 1
  46. } else {
  47. return ((progressView.frame.minX - (leftOprateView.frame.maxX - ((progressView.frame.width - 3) / 2))) / perSecondWidth) / (cutEndTime - cutStartTime)
  48. }
  49. }
  50. /// 拖缀结束的回调 type - 1-拖动左边裁剪结束 2--拖动右边裁剪结束 3-进度条拖动结束 4-滑动结束
  51. var didEndDragging: ((_ type: Int, _ startTime: CGFloat, _ endTime: CGFloat, _ progress: CGFloat) -> Void)?
  52. /// 裁剪实时回调
  53. var cutRangeDidChanged: ((_ startTime: CGFloat, _ endTime: CGFloat, _ cutTotalTime: CGFloat) -> Void)?
  54. /// 进度回调
  55. var progressDidChanged: ((_ progress: CGFloat) -> Void)?
  56. lazy var durationLabel: UILabel = {
  57. let durationLabel = UILabel()
  58. durationLabel.font = UIFont.systemFont(ofSize: 16, weight: .medium)
  59. durationLabel.backgroundColor = BFConfig.shared.cutDurationColor
  60. durationLabel.textColor = UIColor.white
  61. durationLabel.textAlignment = .center
  62. durationLabel.addShadow()
  63. return durationLabel
  64. }()
  65. lazy var leftOprateView: UIImageView = {
  66. let leftOprateView = UIImageView(image:UIImage.moduleImage(named: "videomk_crop_left", moduleName: "BFStuckPointKit",isAssets: false)?.withRenderingMode(.alwaysTemplate))
  67. leftOprateView.tintColor = BFConfig.shared.cutViewTintColor
  68. leftOprateView.contentMode = .scaleAspectFill
  69. leftOprateView.isUserInteractionEnabled = true
  70. leftOprateView.backgroundColor = BFConfig.shared.cutViewStyleColor
  71. let panGes = UIPanGestureRecognizer(target: self, action: #selector(panGesture(gesture:)))
  72. panGes.maximumNumberOfTouches = 1
  73. panGes.minimumNumberOfTouches = 1
  74. leftOprateView.addGestureRecognizer(panGes)
  75. return leftOprateView
  76. }()
  77. lazy var rightOprateView: UIImageView = {
  78. let rightOprateView = UIImageView(image:UIImage.moduleImage(named: "videomk_crop_right", moduleName: "BFStuckPointKit",isAssets: false)?.withRenderingMode(.alwaysTemplate))
  79. rightOprateView.tintColor = BFConfig.shared.cutViewTintColor
  80. rightOprateView.contentMode = .scaleAspectFill
  81. rightOprateView.isUserInteractionEnabled = true
  82. rightOprateView.backgroundColor = BFConfig.shared.cutViewStyleColor
  83. let panGes = UIPanGestureRecognizer(target: self, action: #selector(panGesture(gesture:)))
  84. panGes.maximumNumberOfTouches = 1
  85. panGes.minimumNumberOfTouches = 1
  86. rightOprateView.addGestureRecognizer(panGes)
  87. return rightOprateView
  88. }()
  89. lazy var topLineView: UIImageView = {
  90. let topLineView = UIImageView()
  91. topLineView.backgroundColor = BFConfig.shared.cutViewStyleColor
  92. return topLineView
  93. }()
  94. lazy var bottomLineView: UIImageView = {
  95. let bottomLineView = UIImageView()
  96. bottomLineView.backgroundColor = BFConfig.shared.cutViewStyleColor
  97. return bottomLineView
  98. }()
  99. lazy var progressView: PQCuttingPointView = {
  100. let progressView = PQCuttingPointView(frame: CGRect(x: 0, y: 0, width: progressWidth, height: frame.height))
  101. let panGes = UIPanGestureRecognizer(target: self, action: #selector(panGesture(gesture:)))
  102. panGes.maximumNumberOfTouches = 1
  103. panGes.minimumNumberOfTouches = 1
  104. progressView.addGestureRecognizer(panGes)
  105. return progressView
  106. }()
  107. override private init(frame: CGRect) {
  108. super.init(frame: frame)
  109. }
  110. required init?(coder _: NSCoder) {
  111. fatalError("init(coder:) has not been implemented")
  112. }
  113. init(frame: CGRect, duration: CGFloat, startTime: CGFloat, endTime: CGFloat, minDuration: CGFloat, maxDuration: CGFloat) {
  114. super.init(frame: frame)
  115. isUserInteractionEnabled = true
  116. clipsToBounds = true
  117. backgroundColor = UIColor.clear
  118. totalDuration = duration
  119. cutStartTime = startTime
  120. cutEndTime = endTime
  121. cutMinDuration = minDuration
  122. cutMaxDuration = maxDuration
  123. if cutMaxDuration <= 0 || cutMaxDuration > totalDuration {
  124. cutMaxDuration = totalDuration
  125. }
  126. if cutMinDuration <= 0 || (cutMinDuration > totalDuration) {
  127. cutMinDuration = 7
  128. }
  129. if cutEndTime <= 0 {
  130. cutEndTime = cutStartTime + cutMinDuration
  131. }
  132. perSecondWidth = 7
  133. cutTotalTime = cutEndTime - cutStartTime
  134. configSubview()
  135. BFLog(message: "======\(frame),perSecondWidth = \(perSecondWidth),cutMaxDuration = \(cutMaxDuration * perSecondWidth)")
  136. }
  137. /// 初始化视图
  138. /// - Returns: <#description#>
  139. func configSubview() {
  140. if leftOprateView.superview == nil {
  141. addSubview(leftOprateView)
  142. }
  143. if rightOprateView.superview == nil {
  144. addSubview(rightOprateView)
  145. }
  146. if topLineView.superview == nil {
  147. addSubview(topLineView)
  148. }
  149. if bottomLineView.superview == nil {
  150. addSubview(bottomLineView)
  151. }
  152. if durationLabel.superview == nil {
  153. addSubview(durationLabel)
  154. }
  155. if progressView.superview == nil {
  156. addSubview(progressView)
  157. }
  158. leftOprateView.frame = CGRect(x: leftMargin + perSecondWidth * cutStartTime, y: topMargin, width: cutingOprateWidth, height: frame.height - topMargin - bottomMargin)
  159. rightOprateView.frame = CGRect(x: leftOprateView.frame.maxX + perSecondWidth * (cutEndTime - cutStartTime), y: leftOprateView.frame.minY, width: cutingOprateWidth, height: frame.height - topMargin - bottomMargin)
  160. // 更新子视图布局
  161. updateSubViewFrame()
  162. }
  163. /// 更新子视图布局
  164. /// - Returns: <#description#>
  165. func updateSubViewFrame() {
  166. topLineView.frame = CGRect(x: leftOprateView.frame.maxX, y: leftOprateView.frame.minY, width: rightOprateView.frame.minX - leftOprateView.frame.maxX, height: lineHeight)
  167. bottomLineView.frame = CGRect(x: topLineView.frame.minX, y: leftOprateView.frame.maxY - lineHeight, width: topLineView.frame.width, height: lineHeight)
  168. progressView.frame = CGRect(x: leftOprateView.frame.maxX - ((progressView.frame.width - 3) / 2), y: 0, width: progressWidth, height: frame.height)
  169. durationLabel.frame = CGRect(x: topLineView.frame.minX, y: topLineView.frame.maxY, width: topLineView.frame.width, height: bottomLineView.frame.minY - topLineView.frame.maxY)
  170. durationLabel.text = "\(lround(Double(cutTotalTime)))s"
  171. }
  172. /// 更新进度
  173. /// progress <#progress description#>
  174. func updateProgress(progress: CGFloat) {
  175. if currentPanView != nil {
  176. return
  177. }
  178. let width = rightOprateView.frame.minX - leftOprateView.frame.maxX
  179. var newX = leftOprateView.frame.maxX - ((progressView.frame.width - 3) / 2) + progress * width
  180. BFLog(message: "progress = \(progress),newX = \(newX)")
  181. if newX.isNaN || newX <= (leftOprateView.frame.maxX - ((progressView.frame.width - 3) / 2)) {
  182. newX = (leftOprateView.frame.maxX - ((progressView.frame.width - 3) / 2))
  183. }
  184. if newX >= (rightOprateView.frame.minX - (((progressView.frame.width - 3) / 2) + 3)) {
  185. newX = (rightOprateView.frame.minX - (((progressView.frame.width - 3) / 2) + 3))
  186. }
  187. progressView.frame.origin.x = newX
  188. }
  189. deinit {
  190. BFLog(message: "卡点裁剪-裁剪时长视图销毁")
  191. }
  192. }
  193. extension PQVideoCutingOprateView {
  194. /// 操作手势
  195. /// - Parameter ges: <#ges description#>
  196. /// - Returns: <#description#>
  197. @objc func panGesture(gesture: UIPanGestureRecognizer) {
  198. switch gesture.state {
  199. case .began:
  200. preOriginX = 0
  201. currentPanView = gesture.view
  202. case .changed:
  203. if currentPanView == leftOprateView || currentPanView == rightOprateView || currentPanView == progressView {
  204. let point = gesture.translation(in: superview)
  205. var offsetX = point.x - preOriginX
  206. preOriginX = point.x
  207. if currentPanView == leftOprateView {
  208. var oprateFrame = leftOprateView.frame
  209. oprateFrame.origin.x = oprateFrame.origin.x + offsetX
  210. if oprateFrame.origin.x <= leftMargin {
  211. offsetX += leftMargin - oprateFrame.origin.x
  212. oprateFrame.origin.x = leftMargin
  213. }
  214. let minLength = rightOprateView.frame.minX - cutingOprateWidth - cutMinDuration * perSecondWidth
  215. if oprateFrame.origin.x >= minLength {
  216. offsetX -= oprateFrame.origin.x - minLength
  217. oprateFrame.origin.x = minLength
  218. }
  219. let maxLength = rightOprateView.frame.minX - cutingOprateWidth - cutMaxDuration * perSecondWidth
  220. if oprateFrame.origin.x <= maxLength {
  221. offsetX += maxLength - oprateFrame.origin.x
  222. oprateFrame.origin.x = maxLength
  223. }
  224. let time = offsetX / perSecondWidth
  225. cutStartTime = cutStartTime + time
  226. leftOprateView.frame = oprateFrame
  227. } else if currentPanView == rightOprateView {
  228. var oprateFrame = rightOprateView.frame
  229. oprateFrame.origin.x += offsetX
  230. var rightImageMaxX = leftOprateView.frame.maxX + cutMaxDuration * perSecondWidth
  231. if rightImageMaxX > frame.width - rightMargin - cutingOprateWidth {
  232. rightImageMaxX = frame.width - rightMargin - cutingOprateWidth
  233. }
  234. if oprateFrame.origin.x >= rightImageMaxX {
  235. offsetX -= oprateFrame.origin.x - rightImageMaxX
  236. oprateFrame.origin.x = rightImageMaxX
  237. }
  238. let rightImageMinX = leftOprateView.frame.maxX + cutMinDuration * perSecondWidth
  239. if oprateFrame.origin.x <= rightImageMinX {
  240. offsetX += rightImageMinX - oprateFrame.origin.x
  241. oprateFrame.origin.x = rightImageMinX
  242. }
  243. let time = offsetX / perSecondWidth
  244. cutEndTime = cutEndTime + time
  245. rightOprateView.frame = oprateFrame
  246. } else if currentPanView == progressView {
  247. var progressFrame = progressView.frame
  248. BFLog(message: "progressFrame = \(progressFrame),offsetX = \(offsetX)")
  249. progressFrame.origin.x += offsetX
  250. if progressFrame.origin.x <= (leftOprateView.frame.maxX - ((progressView.frame.width - 3) / 2)) {
  251. progressFrame.origin.x = (leftOprateView.frame.maxX - ((progressView.frame.width - 3) / 2))
  252. }
  253. if progressFrame.origin.x >= (rightOprateView.frame.minX - (((progressView.frame.width - 3) / 2) + 3)) {
  254. progressFrame.origin.x = (rightOprateView.frame.minX - (((progressView.frame.width - 3) / 2) + 3))
  255. }
  256. progressView.frame = progressFrame
  257. BFLog(message: "======\(progressView.frame.minX - (leftOprateView.frame.maxX - ((progressView.frame.width - 3) / 2)))")
  258. if progressDidChanged != nil {
  259. progressDidChanged!(progress)
  260. }
  261. }
  262. if currentPanView != progressView {
  263. cutTotalTime = cutEndTime - cutStartTime
  264. // 更新子视图布局
  265. updateSubViewFrame()
  266. if cutRangeDidChanged != nil {
  267. BFLog(message: "cutStartTime = \(cutStartTime),cutEndTime = \(cutEndTime)")
  268. cutRangeDidChanged!(cutStartTime, cutEndTime, cutTotalTime)
  269. }
  270. }
  271. }
  272. case .ended:
  273. if currentPanView == leftOprateView || currentPanView == rightOprateView || currentPanView == progressView {
  274. if didEndDragging != nil {
  275. didEndDragging!(currentPanView == leftOprateView ? 1 : (currentPanView == rightOprateView ? 2 : 3), cutStartTime, cutEndTime, progress)
  276. }
  277. }
  278. currentPanView = nil
  279. default:
  280. break
  281. }
  282. }
  283. }