PQStuckPointCuttingView.swift 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272
  1. //
  2. // PQStuckPointCuttingView.swift
  3. // PQSpeed
  4. //
  5. // Created by SanW on 2021/5/8.
  6. // Copyright © 2021 BytesFlow. All rights reserved.
  7. //
  8. import UIKit
  9. import BFCommonKit
  10. class PQStuckPointCuttingView: UIView {
  11. // 视频时长
  12. private var videoDuration: CGFloat = 0
  13. // 卡点开始时间 默认 0
  14. private var stuckPointStartTime: CGFloat = 0
  15. // 卡点结束时间
  16. private var stuckPointEndTime: CGFloat = 0
  17. // 裁剪开始时间 默认 0
  18. private var cutStartTime: CGFloat = 0
  19. // 裁剪结束时间
  20. private var cutEndTime: CGFloat = 0
  21. /// 裁剪结束最终的开始时间
  22. private var cutFinishedStartTime: CGFloat {
  23. (scrollView.contentOffset.x / perSecondWidth) + (((scrollView.frame.width - videoCropView.frame.width) / 2 + 15) / perSecondWidth) + cutStartTime
  24. }
  25. /// 裁剪结束最终的结束时间
  26. private var cutFinishedEndTime: CGFloat {
  27. cutFinishedStartTime + (cutEndTime - cutStartTime)
  28. }
  29. // 播放进度
  30. private var videoProgress: CGFloat = 0
  31. // 最大时长 默认 40s
  32. private var maxCutTime: CGFloat = cDefaultMargin * 4
  33. // 最小时长 默认 10s
  34. private var minCutTime: CGFloat = 10
  35. /// 时间间隔
  36. private var timeRange: CGFloat = cDefaultMargin
  37. /// 刻度高
  38. private var rateHeight: CGFloat = cDefaultMargin * 5
  39. /// 时间线宽
  40. private var timeLineWidth: CGFloat = 35
  41. /// 时间线间隔
  42. private var timeLineMargin: CGFloat = 35
  43. /// 时间线高
  44. private var timeHeight: CGFloat = cDefaultMargin * 4
  45. /// 频率宽
  46. private var frequencyWidth: CGFloat = 1
  47. /// 频率间隔
  48. private var frequencyMargin: CGFloat = 4
  49. /// 左右间隔
  50. private var margin: CGFloat = cDefaultMargin * 3
  51. /// 每秒宽度
  52. private var perSecondWidth: CGFloat = 0
  53. /// 滑动区域大小
  54. private var contentWidth: CGFloat = 0
  55. /// 拖拽改变实时的回调
  56. var videoRangeDidChanged: ((_ startTime: CGFloat, _ endTime: CGFloat) -> Void)?
  57. /// 进度改变实时的回调
  58. var videoProgressDidChanged: ((_ progress: CGFloat) -> Void)?
  59. /// 拖缀结束的回调 type - 1-拖动左边裁剪结束 2--拖动右边裁剪结束 3-进度条拖动结束 4-滑动结束
  60. var videoDidEndDragging: ((_ type: Int, _ startTime: CGFloat, _ endTime: CGFloat, _ progress: CGFloat) -> Void)?
  61. /// 滚动视图
  62. lazy var scrollView: UIScrollView = {
  63. let scrollView = UIScrollView(frame: bounds)
  64. scrollView.showsVerticalScrollIndicator = false
  65. scrollView.showsHorizontalScrollIndicator = false
  66. scrollView.bounces = false
  67. scrollView.delegate = self
  68. if #available(iOS 11.0, *) {
  69. scrollView.contentInsetAdjustmentBehavior = .never
  70. } else {
  71. // automaticallyAdjustsScrollViewInsets = false
  72. }
  73. return scrollView
  74. }()
  75. lazy var rateView: UIView = {
  76. let rateView = UIView(frame: CGRect(x: 0, y: videoCropView.frame.minY + 16.5, width: scrollView.contentSize.width, height: rateHeight))
  77. rateView.backgroundColor = PQBFConfig.shared.otherTintColor
  78. return rateView
  79. }()
  80. lazy var videoCropView: PQVideoCutingOprateView = {
  81. var originX: CGFloat = perSecondWidth * (stuckPointStartTime + cutStartTime) + margin
  82. if contentWidth > scrollView.frame.width {
  83. originX = perSecondWidth * (stuckPointStartTime - cutStartTime) + margin
  84. }
  85. let videoCropView: PQVideoCutingOprateView = PQVideoCutingOprateView(frame: CGRect(x: margin, y: frame.height - 90, width: contentWidth - margin * 2, height: 90), duration: videoDuration, startTime: cutStartTime, endTime: cutEndTime, minDuration: minCutTime, maxDuration: maxCutTime)
  86. videoCropView.cutRangeDidChanged = { [weak self] cutStartTime, cutEndTime, _ in
  87. self?.cutStartTime = cutStartTime
  88. self?.cutEndTime = cutEndTime
  89. self?.videoProgress = 0
  90. // 计算当前时间
  91. if self?.videoRangeDidChanged != nil {
  92. self?.videoRangeDidChanged!(cutStartTime, cutEndTime)
  93. }
  94. }
  95. videoCropView.progressDidChanged = { [weak self] progress in
  96. self?.videoProgress = progress
  97. if self?.videoProgressDidChanged != nil {
  98. self?.videoProgressDidChanged!(progress)
  99. }
  100. }
  101. videoCropView.didEndDragging = { [weak self] type, startTime, endTime, progress in
  102. self?.videoProgress = 0
  103. if self?.videoDidEndDragging != nil {
  104. self?.videoDidEndDragging!(type, startTime, endTime, progress)
  105. }
  106. }
  107. return videoCropView
  108. }()
  109. override private init(frame: CGRect) {
  110. super.init(frame: frame)
  111. }
  112. required init?(coder _: NSCoder) {
  113. fatalError("init(coder:) has not been implemented")
  114. }
  115. init(frame: CGRect, duration: CGFloat, startTime: CGFloat, endTime: CGFloat) {
  116. super.init(frame: frame)
  117. videoDuration = duration
  118. if videoDuration < maxCutTime {
  119. maxCutTime = videoDuration
  120. }
  121. // 更新卡点值
  122. updateEndTime(startTime: startTime, endTime: endTime)
  123. }
  124. /// 更新卡点值
  125. /// - Parameter endTime: <#endTime description#>
  126. /// - Returns: <#description#>
  127. func updateEndTime(startTime: CGFloat, endTime: CGFloat) {
  128. stuckPointStartTime = startTime
  129. stuckPointEndTime = endTime
  130. if (stuckPointEndTime - stuckPointStartTime) > maxCutTime {
  131. stuckPointEndTime = stuckPointStartTime + maxCutTime
  132. }
  133. cutStartTime = startTime
  134. cutEndTime = endTime
  135. if cutEndTime > videoDuration {
  136. cutEndTime = videoDuration
  137. }
  138. backgroundColor = PQBFConfig.shared.styleBackGroundColor
  139. addSubview(scrollView)
  140. addData()
  141. }
  142. func addData() {
  143. perSecondWidth = (timeLineWidth + timeLineMargin) / timeRange
  144. frequencyMargin = perSecondWidth - frequencyWidth
  145. let tempWidth = videoDuration * perSecondWidth + timeLineWidth
  146. let totalCount = Int(videoDuration / timeRange)
  147. // let remainder = videoDuration - (CGFloat(totalCount) * timeRange)
  148. // if remainder != 0 {
  149. // totalCount = totalCount + 1
  150. // }
  151. if (tempWidth + margin * 2) < scrollView.frame.width {
  152. margin = (scrollView.frame.width - tempWidth) / 2
  153. }
  154. contentWidth = tempWidth + margin * 2
  155. if contentWidth < scrollView.frame.width {
  156. contentWidth = scrollView.frame.width
  157. }
  158. scrollView.contentSize = CGSize(width: contentWidth, height: scrollView.frame.height)
  159. for index in 0 ... totalCount {
  160. let titleLab = UILabel(frame: CGRect(x: CGFloat(index) * (timeLineWidth + timeLineMargin) + margin, y: 0, width: timeLineWidth, height: timeHeight))
  161. titleLab.font = UIFont.systemFont(ofSize: 12)
  162. titleLab.textAlignment = .center
  163. titleLab.numberOfLines = 2
  164. titleLab.textColor = UIColor.hexColor(hexadecimal: "#999999")
  165. titleLab.text = "\((Float64(index) * Float64(timeRange)).formatDurationToHMS())\n·"
  166. scrollView.addSubview(titleLab)
  167. }
  168. if perSecondWidth > 0, cutEndTime > 0, cutStartTime < cutEndTime {
  169. /// 处理音频频率
  170. configVoiceFrequency()
  171. // 每秒占有宽度
  172. if scrollView.frame.width < scrollView.contentSize.width {
  173. var offsetX = perSecondWidth * (stuckPointStartTime + (stuckPointEndTime - stuckPointStartTime) / 2) - scrollView.frame.width / 2 + timeLineWidth
  174. if offsetX < 0 {
  175. offsetX = 0
  176. }
  177. if offsetX > (scrollView.contentSize.width - scrollView.frame.width) {
  178. offsetX = (scrollView.contentSize.width - scrollView.frame.width)
  179. }
  180. scrollView.contentOffset = CGPoint(x: offsetX, y: 0)
  181. }
  182. scrollView.addSubview(rateView)
  183. scrollView.addSubview(videoCropView)
  184. }
  185. }
  186. /// 处理音频频率
  187. /// - Returns: <#description#>
  188. func configVoiceFrequency() {
  189. let waveTotalCount = Int(videoDuration) / cFrequency.count
  190. var remainder = Int(ceil(videoDuration - CGFloat(waveTotalCount * cFrequency.count)))
  191. var totalWave: [CGFloat] = Array<CGFloat>.init()
  192. for _ in 0 ..< waveTotalCount {
  193. totalWave = totalWave + cFrequency
  194. }
  195. if remainder > cFrequency.count - 1 {
  196. remainder = cFrequency.count - 1
  197. }
  198. if remainder > 0 {
  199. totalWave = totalWave + cFrequency[0 ... remainder]
  200. }
  201. createWave(waveArr: totalWave)
  202. }
  203. /// 生成波纹
  204. /// - Parameter waveArr: <#waveArr description#>
  205. /// - Returns: <#description#>
  206. func createWave(waveArr: [CGFloat]) {
  207. for (i, power) in waveArr.enumerated() {
  208. // 画布高度
  209. let hight: CGFloat = rateView.frame.height
  210. // 开始 Y 值
  211. var startY: CGFloat = (hight - power) / 2.0
  212. if startY < 0 { startY = 0 }
  213. // 结束 Y 值
  214. var endY: CGFloat = startY + power
  215. if endY > CGFloat(hight) { endY = hight }
  216. // 线的路径
  217. let linePath = UIBezierPath()
  218. // 起点
  219. let originX: CGFloat = CGFloat(i * Int(frequencyWidth + frequencyMargin)) + margin + timeLineWidth / 2
  220. linePath.move(to: CGPoint(x: originX, y: startY))
  221. // 终点
  222. linePath.addLine(to: CGPoint(x: originX, y: endY))
  223. let lineLayer = CAShapeLayer()
  224. lineLayer.lineWidth = frequencyWidth
  225. lineLayer.strokeColor = UIColor.hexColor(hexadecimal: "#999999").cgColor
  226. lineLayer.path = linePath.cgPath
  227. lineLayer.fillColor = UIColor.black.cgColor
  228. rateView.layer.insertSublayer(lineLayer, at: 0)
  229. }
  230. }
  231. deinit {
  232. BFLog(message: "卡点裁剪-裁剪视图销毁")
  233. }
  234. }
  235. // MARK: - scrollView滑动代理
  236. /// scrollView滑动代理
  237. extension PQStuckPointCuttingView: UIScrollViewDelegate {
  238. func scrollViewDidScroll(_: UIScrollView) {
  239. // 计算当前开始时间
  240. // if scrollView.contentOffset.x > margin {
  241. // if videoRangeDidChanged != nil {
  242. // videoRangeDidChanged!(cutFinishedStartTime, cutFinishedEndTime)
  243. // }
  244. // NSObject.cancelPreviousPerformRequests(withTarget: self)
  245. // perform(#selector(scrollViewDidEndScrollingAnimation(scrollView:)), with: nil, afterDelay: 0.1)
  246. // }
  247. }
  248. @objc func scrollViewDidEndScrollingAnimation(scrollView _: UIScrollView) {
  249. BFLog(message: "拖拽结束 - 回调")
  250. // videoProgress = 0
  251. // if videoDidEndDragging != nil {
  252. // videoDidEndDragging!(3, cutFinishedStartTime, cutFinishedEndTime, videoProgress)
  253. // }
  254. }
  255. }