PQStuckPointCuttingView.swift 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373
  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. class PQStuckPointCuttingView: UIView {
  10. // 视频时长
  11. var videoDuration: CGFloat = 0
  12. // 卡点开始时间 默认 0
  13. private var stuckPointStartTime: CGFloat = 0
  14. // 卡点结束时间
  15. private var stuckPointEndTime: CGFloat = 0
  16. // 裁剪开始时间 默认 0
  17. private var cutStartTime: CGFloat = 0
  18. // /// 裁剪结束最终的开始时间
  19. // private var cutFinishedStartTime: CGFloat {
  20. // (scrollView.contentOffset.x / perSecondWidth) + (((scrollView.frame.width - videoCropView.frame.width) / 2 + 15) / perSecondWidth) + cutStartTime
  21. // }
  22. //
  23. // /// 裁剪结束最终的结束时间
  24. // private var cutFinishedEndTime: CGFloat {
  25. // cutFinishedStartTime + (cutEndTime - cutStartTime)
  26. // }
  27. // 播放进度
  28. private var videoProgress: CGFloat = 0
  29. // 最大时长 默认 40s
  30. private var maxCutTime: CGFloat = cDefaultMargin * 400
  31. // 最小时长 默认 10s
  32. private var minCutTime: CGFloat = 10
  33. /// 时间间隔
  34. private var timeRange: CGFloat = cDefaultMargin
  35. /// 刻度高
  36. private var rateHeight: CGFloat = 23
  37. /// 时间线宽
  38. private var timeLineWidth: CGFloat = 35
  39. /// 时间线间隔
  40. private var timeLineMargin: CGFloat = 35
  41. /// 时间线高
  42. private var timeHeight: CGFloat = cDefaultMargin * 4
  43. /// 频率线宽
  44. private var frequencyWidth: CGFloat = adapterWidth(width: 1.5)
  45. /// 频率间隔
  46. private var frequencyMargin: CGFloat = adapterWidth(width: 3)
  47. /// 竖线和contentview 父视图的左右间隔
  48. private var margin: CGFloat = (cScreenWidth - adapterWidth(width: 250)) / 2
  49. /// 滑动区域大小
  50. private var contentWidth: CGFloat = 0
  51. // 竖线一个间隔代表多少 S 是动态的
  52. private var oneMarginTime: CGFloat = 0
  53. private var isDrawLine: Bool = false
  54. // 保存已经绘制的竖线用于变色使用
  55. var lineLayerArray: Array = Array<CAShapeLayer>.init()
  56. // 裁剪区的相素大小
  57. var cropViewWidth: CGFloat = adapterWidth(width: 250)
  58. /// 拖拽改变实时的回调
  59. var videoRangeDidChanged: ((_ startTime: CGFloat, _ endTime: CGFloat) -> Void)?
  60. /// 进度改变实时的回调
  61. var videoProgressDidChanged: ((_ progress: CGFloat) -> Void)?
  62. /// 拖缀结束的回调 type - 1-拖动左边裁剪结束 2--拖动右边裁剪结束 3-进度条拖动结束 4-滑动结束
  63. var videoDidEndDragging: ((_ type: Int, _ startTime: CGFloat, _ endTime: CGFloat, _ progress: CGFloat) -> Void)?
  64. // 开始划动
  65. var videoDidBeginDrag: (() -> Void)?
  66. // 选择区内的线个数
  67. var wavSelectCount: Int = 0
  68. // 整首歌的线的个数
  69. var wavTotalCount: Int = 0
  70. //推荐虚线的位置
  71. var startLineX:CGFloat = 0.0
  72. /// 滚动视图
  73. lazy var scrollView: UIScrollView = {
  74. let scrollView = UIScrollView(frame: bounds)
  75. scrollView.showsVerticalScrollIndicator = false
  76. scrollView.showsHorizontalScrollIndicator = false
  77. scrollView.bounces = false
  78. scrollView.delegate = self
  79. if #available(iOS 11.0, *) {
  80. scrollView.contentInsetAdjustmentBehavior = .never
  81. } else {
  82. // automaticallyAdjustsScrollViewInsets = false
  83. }
  84. scrollView.backgroundColor = .clear
  85. return scrollView
  86. }()
  87. //
  88. lazy var rateView: UIView = {
  89. let rateView = UIView(frame: CGRect(x: 0, y: 22, width: scrollView.contentSize.width, height: rateHeight))
  90. rateView.backgroundColor = .clear
  91. return rateView
  92. }()
  93. // 总时长
  94. lazy var tatalTimeLabel: UILabel = {
  95. let tatalTimeLabel = UILabel()
  96. tatalTimeLabel.font = UIFont.systemFont(ofSize: 11)
  97. tatalTimeLabel.textAlignment = .right
  98. tatalTimeLabel.textColor = UIColor.hexColor(hexadecimal: PQBFConfig.shared.styleColor.rawValue)
  99. return tatalTimeLabel
  100. }()
  101. // 显示选择框
  102. lazy var videoCropView: UIView = {
  103. let videoCropView: UIView = UIView(frame: CGRect(x: (cScreenWidth - cropViewWidth) / 2, y: 0, width: cropViewWidth, height: 80))
  104. videoCropView.isUserInteractionEnabled = false
  105. videoCropView.layer.borderColor = UIColor.hexColor(hexadecimal: PQBFConfig.shared.styleColor.rawValue).cgColor
  106. videoCropView.layer.borderWidth = 2
  107. videoCropView.layer.cornerRadius = 8
  108. return videoCropView
  109. }()
  110. //两边的mask
  111. lazy var leftMaskView: UIView = {
  112. let leftMaskView: UIView = UIView(frame: CGRect(x:0, y: 0, width: (cScreenWidth - cropViewWidth) / 2, height: 80))
  113. leftMaskView.backgroundColor = UIColor.white
  114. leftMaskView.alpha = 0.7
  115. return leftMaskView
  116. }()
  117. //右边的mask
  118. lazy var rightMaskView: UIView = {
  119. let rightMaskView: UIView = UIView(frame: CGRect(x:videoCropView.frame.maxX, y: 0, width: (cScreenWidth - cropViewWidth) / 2, height: 80))
  120. rightMaskView.backgroundColor = UIColor.white
  121. rightMaskView.alpha = 0.7
  122. return rightMaskView
  123. }()
  124. private override init(frame: CGRect) {
  125. super.init(frame: frame)
  126. }
  127. required init?(coder _: NSCoder) {
  128. fatalError("init(coder:) has not been implemented")
  129. }
  130. init(frame: CGRect, duration: CGFloat, startTime: CGFloat, endTime: CGFloat) {
  131. super.init(frame: frame)
  132. videoDuration = duration
  133. if videoDuration < maxCutTime {
  134. maxCutTime = videoDuration
  135. }
  136. // 更新卡点值
  137. updateEndTime(startTime: startTime, endTime: endTime)
  138. }
  139. /// 更新卡点值
  140. /// - Parameter endTime: endTime description
  141. /// - Returns: <#description#>
  142. func updateEndTime(startTime: CGFloat, endTime: CGFloat) {
  143. stuckPointStartTime = startTime
  144. stuckPointEndTime = endTime
  145. BFLog(message: "推荐开始\(stuckPointStartTime) 结束\(stuckPointEndTime) 时长为:\(stuckPointEndTime - stuckPointStartTime)")
  146. backgroundColor = PQBFConfig.shared.styleBackGroundColor
  147. addSubview(scrollView)
  148. addSubview(videoCropView)
  149. addSubview(leftMaskView)
  150. addSubview(rightMaskView)
  151. videoCropView.addSubview(tatalTimeLabel)
  152. addData()
  153. tatalTimeLabel.snp.remakeConstraints { make in
  154. make.width.equalTo(40)
  155. make.height.equalTo(15)
  156. make.top.equalTo(videoCropView.snp_top).offset(6)
  157. make.right.equalTo(videoCropView.snp_right).offset(-6)
  158. }
  159. tatalTimeLabel.text = "\(Float64(stuckPointEndTime - stuckPointStartTime).formatDurationToHMS())"
  160. }
  161. func addData() {
  162. // 1,选择区内的线个数
  163. wavSelectCount = Int(cropViewWidth / (frequencyWidth + frequencyMargin))
  164. // 2竖线一个间隔代表多少 S 是动态的
  165. oneMarginTime = (stuckPointEndTime - stuckPointStartTime) / CGFloat(wavSelectCount)
  166. // 3,一共绘制的竖线个数
  167. wavTotalCount = Int(videoDuration / oneMarginTime)
  168. timeRange = oneMarginTime * 10
  169. // 显示时间 label 的个数 , -1 不够整倍数就不显示时间了
  170. let timeLabelCount = Int(wavTotalCount / 10)
  171. contentWidth = CGFloat(wavTotalCount) * (frequencyWidth + frequencyMargin) + margin * 2 + timeLineWidth / 2
  172. if contentWidth < scrollView.frame.width {
  173. contentWidth = scrollView.frame.width
  174. }
  175. scrollView.contentSize = CGSize(width: contentWidth, height: scrollView.frame.height)
  176. for index in 0 ... timeLabelCount {
  177. scrollView.viewWithTag(100 + index)?.removeFromSuperview()
  178. let titleLab = UILabel(frame: CGRect(x: CGFloat(index) * (frequencyWidth + frequencyMargin) * 10 + margin - timeLineWidth / 2, y: rateView.frame.maxY, width: timeLineWidth, height: 30))
  179. titleLab.font = UIFont.systemFont(ofSize: 11)
  180. titleLab.textAlignment = .center
  181. titleLab.numberOfLines = 1
  182. titleLab.tag = 100 + index
  183. titleLab.backgroundColor = .clear
  184. titleLab.textColor = UIColor.hexColor(hexadecimal: "#999999")
  185. titleLab.text = "\((Float64(index) * Float64(timeRange)).formatDurationToHMS())"
  186. scrollView.addSubview(titleLab)
  187. }
  188. if oneMarginTime > 0 {
  189. // 1,处理音频频率
  190. configVoiceFrequency()
  191. // 2,滚动到推荐位置
  192. scrollView.contentOffset = CGPoint(x: startLineX - margin, y: 0)
  193. scrollView.addSubview(rateView)
  194. }
  195. }
  196. /// 处理音频频率
  197. /// - Returns: <#description#>
  198. func configVoiceFrequency() {
  199. // 整倍数
  200. let waveTotalCount = Int(wavTotalCount) / cFrequency.count
  201. // 余多少个未画的
  202. var remainder = Int(ceil(CGFloat(wavTotalCount) - CGFloat(waveTotalCount * cFrequency.count)))
  203. var totalWave: [CGFloat] = Array<CGFloat>.init()
  204. // 1,先画整倍数个竖线
  205. for _ in 0 ..< waveTotalCount {
  206. totalWave = totalWave + cFrequency
  207. }
  208. if remainder > cFrequency.count - 1 {
  209. remainder = cFrequency.count - 1
  210. }
  211. // 1,再画余数个竖线
  212. if remainder > 0 {
  213. totalWave = totalWave + cFrequency[0 ... remainder]
  214. }
  215. createWave(waveArr: totalWave)
  216. }
  217. /// 更新进度绘制不同色值
  218. /// progress <#progress description#>
  219. func updateProgress(progress: CGFloat) {
  220. for (i, layer) in lineLayerArray.enumerated() {
  221. if CGFloat(i) * oneMarginTime <= stuckPointStartTime + progress * CGFloat(stuckPointEndTime - stuckPointStartTime) && CGFloat(i) * oneMarginTime >= stuckPointStartTime {
  222. BFLog(message: "progress is \(progress) 命中的位置:\(CGFloat(i) * oneMarginTime)")
  223. layer.strokeColor = UIColor.hexColor(hexadecimal: PQBFConfig.shared.styleColor.rawValue).cgColor
  224. layer.setNeedsDisplay()
  225. }
  226. }
  227. }
  228. // 竖线恢复到原有色值
  229. func resetDefaultsColor() {
  230. for layer in lineLayerArray {
  231. layer.strokeColor = UIColor.hexColor(hexadecimal: "#999999").cgColor
  232. layer.setNeedsDisplay()
  233. }
  234. }
  235. /// 生成波纹
  236. /// - Parameter waveArr: <#waveArr description#>
  237. /// - Returns: <#description#>
  238. func createWave(waveArr: [CGFloat]) {
  239. for (i, power) in waveArr.enumerated() {
  240. // 画布高度
  241. let hight: CGFloat = rateView.frame.height
  242. // 开始 Y 值
  243. var startY: CGFloat = (hight - power) / 2.0
  244. if startY < 0 { startY = 0 }
  245. // 结束 Y 值
  246. var endY: CGFloat = startY + power
  247. if endY > CGFloat(hight) { endY = hight }
  248. // 线的路径
  249. let linePath = UIBezierPath()
  250. // 起点 timeLineWidth / 2 处理显示时间的 label中心为时间点
  251. let originX: CGFloat = CGFloat(Float(i) * Float(frequencyWidth + frequencyMargin)) + margin
  252. linePath.move(to: CGPoint(x: originX, y: startY))
  253. // 终点
  254. linePath.addLine(to: CGPoint(x: originX, y: endY))
  255. let lineLayer = CAShapeLayer()
  256. lineLayer.lineWidth = frequencyWidth
  257. lineLayer.strokeColor = UIColor.hexColor(hexadecimal: "#999999").cgColor
  258. lineLayer.path = linePath.cgPath
  259. lineLayer.fillColor = UIColor.black.cgColor
  260. // 推荐的开始起点是虚线
  261. if oneMarginTime * CGFloat(i) >= stuckPointStartTime && !isDrawLine {
  262. isDrawLine = true
  263. startLineX = originX
  264. linePath.move(to: CGPoint(x: originX, y: -10))
  265. // 终点
  266. linePath.addLine(to: CGPoint(x: originX, y: 30))
  267. lineLayer.path = linePath.cgPath
  268. lineLayer.lineDashPhase = 0
  269. lineLayer.lineDashPattern = [3, 3]
  270. }
  271. lineLayerArray.append(lineLayer)
  272. rateView.layer.insertSublayer(lineLayer, at: 0)
  273. }
  274. }
  275. deinit {
  276. BFLog(message: "卡点裁剪-裁剪视图销毁")
  277. }
  278. //划动结速后处理
  279. func moveEnd() {
  280. //最后一个竖线VIEW
  281. let lastLine:UIView = scrollView.viewWithTag(100 + Int(videoDuration / timeRange) - 1) ?? UIView.init()
  282. //移动后的开始时间
  283. let startTime = videoDuration / lastLine.frame.maxX * scrollView.contentOffset.x
  284. BFLog(message: "拖拽结束 - 回调\(scrollView.contentOffset) \(scrollView.contentSize) 开始时间为:\(startTime) 结束时间为:\(startTime + CGFloat(stuckPointEndTime - stuckPointStartTime))")
  285. if(videoDidEndDragging != nil){
  286. videoDidEndDragging!(1,startTime,startTime + CGFloat(stuckPointEndTime - stuckPointStartTime),0)
  287. }
  288. resetDefaultsColor()
  289. PQEventTrackViewModel.baseReportUpload(businessType: .bt_buttonClick, objectType: .ot_shanyinApp_musicVideoPreview_musicPeriodSelect, pageSource: .sp_shanyinApp_main, extParams: nil, remindmsg: "")
  290. }
  291. }
  292. // MARK: - scrollView滑动代理
  293. /// scrollView滑动代理
  294. extension PQStuckPointCuttingView: UIScrollViewDelegate {
  295. func scrollViewDidScroll(_: UIScrollView) {}
  296. func scrollViewWillBeginDragging(_ :UIScrollView){
  297. if(videoDidBeginDrag != nil){
  298. videoDidBeginDrag!()
  299. }
  300. }
  301. func scrollViewDidEndDecelerating(_: UIScrollView) {
  302. if !scrollView.isDragging, !scrollView.isDecelerating {
  303. moveEnd()
  304. }
  305. }
  306. func scrollViewDidEndDragging(_:UIScrollView,willDecelerate decelerate:Bool){
  307. if !decelerate, !scrollView.isDragging, !scrollView.isDecelerating {
  308. moveEnd()
  309. }
  310. }
  311. func scrollViewDidEndScrollingAnimation(_: UIScrollView) {
  312. BFLog(message: "scrollViewDidEndScrollingAnimation")
  313. }
  314. }