BFMusicCutView.swift 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368
  1. //
  2. // BFMusicCutView.swift
  3. // BFRecordScreenKit
  4. //
  5. // Created by ak on 2022/3/4.
  6. // 功能:音乐裁剪界面,设置开始播放位置
  7. import BFCommonKit
  8. import BFMediaKit
  9. import BFUIKit
  10. import Foundation
  11. import UIKit
  12. // 操作动作类型
  13. public enum MusiceCutActionType: Int {
  14. case MusiceCutActionCancel = 1 // 取消
  15. case MusiceCutActionConfirm = 2 // 确认
  16. }
  17. class BFMusicCutView: UIView, UIGestureRecognizerDelegate {
  18. var waveLayers: [CAShapeLayer] = Array<CAShapeLayer>.init()
  19. // 裁剪时间回调
  20. var cutTimeHandle: ((_ startTime: Float64, _ endTime: Float64, _ bgmData: PQVoiceModel?) -> Void)?
  21. let normalMargin: CGFloat = cDefaultMargin * 2
  22. // 记录设置的起点, 在点击确认后会设置 bgmdata 的 开始时间为本值。
  23. var startCMTime: CMTime = .zero // 开始时间
  24. // 播放音乐
  25. lazy var avPlayer: AVPlayer = {
  26. let avPlayer = AVPlayer()
  27. PQNotification.addObserver(forName: .AVPlayerItemDidPlayToEndTime, object: avPlayer.currentItem, queue: .main) { [weak self] notify in
  28. BFLog(message: "AVPlayerItemDidPlayToEndTime = \(notify)")
  29. guard let wself = self else { return }
  30. if(!wself.isHidden){
  31. wself.avPlayer.seek(to: CMTime(value: CMTimeValue((wself.bgmData?.currentTime ?? 0.0)) * Int64(playerTimescale), timescale: CMTimeScale(playerTimescale)))
  32. wself.resetWave()
  33. wself.avPlayer.play()
  34. }
  35. }
  36. PQNotification.addObserver(forName: .AVPlayerItemNewErrorLogEntry, object: avPlayer.currentItem, queue: .main) { notify in
  37. BFLog(message: "AVPlayerItemNewErrorLogEntry = \(notify)")
  38. }
  39. PQNotification.addObserver(forName: .AVPlayerItemFailedToPlayToEndTime, object: avPlayer.currentItem, queue: .main) { notify in
  40. BFLog(message: "AVPlayerItemFailedToPlayToEndTime = \(notify)")
  41. }
  42. PQNotification.addObserver(forName: .AVPlayerItemPlaybackStalled, object: avPlayer.currentItem, queue: .main) { notify in
  43. BFLog(message: "AVPlayerItemPlaybackStalled = \(notify)")
  44. }
  45. avPlayer.addPeriodicTimeObserver(forInterval: CMTime(value: 1, timescale: CMTimeScale(playerTimescale)), queue: .main) { [weak self] _ in
  46. guard let wself = self else { return }
  47. let currentTime = CMTimeGetSeconds(avPlayer.currentItem?.currentTime() ?? CMTime.zero)
  48. wself.configPlayProgress(currentTime: currentTime)
  49. }
  50. return avPlayer
  51. }()
  52. // 放指针的背景图
  53. lazy var panCutBackView: UIView = {
  54. let panCutBackView = UIView()
  55. panCutBackView.backgroundColor = .clear
  56. let panGes = UIPanGestureRecognizer(target: self, action: #selector(panClick(ges:)))
  57. panCutBackView.addGestureRecognizer(panGes)
  58. return panCutBackView
  59. }()
  60. // 指针
  61. lazy var cutRemindView: UIView = {
  62. let cutRemindView = UIView()
  63. cutRemindView.backgroundColor = UIColor.hexColor(hexadecimal: "#389AFF")
  64. cutRemindView.addCorner(corner: 2)
  65. cutRemindView.frame = CGRect(x: (panCutBackView.frame.width - 2) / 2, y: 0, width: 2, height: panCutBackView.frame.height)
  66. return cutRemindView
  67. }()
  68. // 水波纹的背景
  69. lazy var progressImage: UIImageView = {
  70. let progressImage = UIImageView()
  71. progressImage.frame = CGRect(x: 20, y: currentProgressLab.frame.maxY + 2, width: cScreenWidth - 20 * 2, height: cDefaultMargin * 6)
  72. return progressImage
  73. }()
  74. // 显示进度
  75. lazy var currentProgressLab: UILabel = {
  76. let currentProgressLab = UILabel(frame: CGRect(x: cDefaultMargin * 3, y: cDefaultMargin * 6, width: cDefaultMargin * 6, height: cDefaultMargin * 2))
  77. currentProgressLab.textColor = UIColor.hexColor(hexadecimal: "#616161")
  78. currentProgressLab.font = UIFont.systemFont(ofSize: 13)
  79. currentProgressLab.text = "00:00"
  80. return currentProgressLab
  81. }()
  82. // 操作板背景
  83. let backView = UIButton()
  84. // 当前选择的音乐
  85. public var bgmData: PQVoiceModel? {
  86. didSet {
  87. BFLog(message: "设置的音乐开始时间currentTime:\(bgmData?.currentTime ?? 0) 总时长\(bgmData?.duration ?? "") 音量是:\(bgmData?.volume ?? 0)")
  88. resetWave()
  89. // 默认进来播放音乐
  90. playBGM()
  91. avPlayer.volume = Float(bgmData?.volume ?? 0) / 100.0
  92. addCutViewLayout()
  93. }
  94. }
  95. lazy var titleL: UILabel = {
  96. let l = UILabel()
  97. l.text = "起播点"
  98. l.textAlignment = .center
  99. l.textColor = .white
  100. l.font = UIFont.systemFont(ofSize: 17, weight: .medium)
  101. return l
  102. }()
  103. required init?(coder _: NSCoder) {
  104. fatalError("init(coder:) has not been implemented")
  105. }
  106. override func layoutSubviews() {
  107. super.layoutSubviews()
  108. backView.addCorner(roundingCorners: [.topLeft, .topRight], corner: 10)
  109. addCutViewLayout()
  110. }
  111. func addCutViewLayout() {
  112. let totalWidth = (frame.width - normalMargin * 2 - 6)
  113. let itemWidth = totalWidth / CGFloat(Double("\(bgmData?.duration ?? "1")") ?? 0.0)
  114. panCutBackView.frame = CGRect(x: (normalMargin - cDefaultMargin * 1.5) + CGFloat(bgmData?.currentTime ?? 0) * itemWidth, y: currentProgressLab.frame.maxY + 2, width: cDefaultMargin * 3, height: cDefaultMargin * 6)
  115. cutRemindView.frame = CGRect(x: (panCutBackView.frame.width - 2) / 2, y: 0, width: 2, height: panCutBackView.frame.height)
  116. currentProgressLab.centerX = panCutBackView.centerX
  117. }
  118. func playBGM() {
  119. if (bgmData?.musicPath ?? "").count > 0 {
  120. avPlayer.pause()
  121. avPlayer.replaceCurrentItem(with: AVPlayerItem(url: URL(string: (bgmData?.musicPath ?? ""))!))
  122. avPlayer.seek(to: CMTime(value: CMTimeValue((bgmData?.currentTime ?? 0.0) * playerTimescale), timescale: CMTimeScale(playerTimescale)))
  123. avPlayer.play()
  124. }
  125. }
  126. override init(frame: CGRect) {
  127. super.init(frame: frame)
  128. backgroundColor = UIColor(red: 0, green: 0, blue: 0, alpha: 0)
  129. backView.backgroundColor = UIColor.hexColor(hexadecimal: "#121212")
  130. addSubview(backView)
  131. backView.snp.makeConstraints { make in
  132. make.right.equalTo(self.snp.right)
  133. make.bottom.equalTo(self.snp.bottom)
  134. make.width.equalTo(cScreenWidth)
  135. make.height.equalTo(220)
  136. }
  137. backView.setNeedsUpdateConstraints()
  138. // 取消
  139. let cancelBtn = UIButton()
  140. cancelBtn.backgroundColor = .clear
  141. cancelBtn.setTitle("取消", for: .normal)
  142. cancelBtn.setTitleColor(.white, for: .normal)
  143. cancelBtn.titleLabel?.font = UIFont.boldSystemFont(ofSize: 17)
  144. cancelBtn.addTarget(self, action: #selector(cancelAction), for: .touchDown)
  145. backView.addSubview(cancelBtn)
  146. cancelBtn.snp.makeConstraints { make in
  147. make.left.equalToSuperview().offset(18)
  148. make.width.equalTo(35)
  149. make.height.equalTo(24)
  150. make.top.equalToSuperview().offset(18)
  151. }
  152. backView.addSubview(titleL)
  153. titleL.snp.makeConstraints { make in
  154. make.centerX.equalToSuperview()
  155. make.width.equalTo(54)
  156. make.height.equalTo(24)
  157. make.top.equalTo(cancelBtn)
  158. }
  159. // 确认
  160. let okBtn = UIButton()
  161. okBtn.backgroundColor = .clear
  162. okBtn.setTitle("确认", for: .normal)
  163. okBtn.setTitleColor(UIColor.hexColor(hexadecimal: "#389AFF"), for: .normal)
  164. okBtn.titleLabel?.font = UIFont.systemFont(ofSize: 17)
  165. okBtn.addTarget(self, action: #selector(okBtnAction), for: .touchUpInside)
  166. backView.addSubview(okBtn)
  167. okBtn.snp.makeConstraints { make in
  168. make.right.equalToSuperview().offset(-18)
  169. make.width.equalTo(35)
  170. make.height.equalTo(24)
  171. make.top.equalToSuperview().offset(18)
  172. }
  173. backView.addSubview(panCutBackView)
  174. panCutBackView.addSubview(cutRemindView)
  175. backView.addSubview(currentProgressLab)
  176. backView.addSubview(progressImage)
  177. if (progressImage.layer.sublayers?.count ?? 0) > 0 {
  178. let totalCount = progressImage.layer.sublayers?.count ?? 0
  179. for index in 0 ..< Int(totalCount) {
  180. (progressImage.layer.sublayers?[index] as? CAShapeLayer)?.strokeColor = UIColor.white.cgColor
  181. (progressImage.layer.sublayers?[index] as? CAShapeLayer)?.setNeedsDisplay()
  182. }
  183. } else {
  184. let totalCount = Int(cScreenWidth - normalMargin * 2) / (cFrequency.count * 2)
  185. let remainder = Int(cScreenWidth - normalMargin * 2) % (cFrequency.count * 2)
  186. var totalWave: [CGFloat] = Array<CGFloat>.init()
  187. for _ in 0 ..< totalCount {
  188. totalWave = totalWave + cFrequency
  189. }
  190. if remainder > 0 {
  191. totalWave = totalWave + cFrequency[0 ... (remainder / 2)]
  192. }
  193. createWave(waveArr: totalWave)
  194. }
  195. }
  196. /// 生成波纹
  197. /// - Parameter waveArr: <#waveArr description#>
  198. /// - Returns: <#description#>
  199. func createWave(waveArr: [CGFloat]) {
  200. for (i, power) in waveArr.enumerated() {
  201. // 画布高度
  202. let hight: CGFloat = progressImage.frame.height
  203. // 开始 Y 值
  204. var startY: CGFloat = (hight - power) / 2.0
  205. if startY < 0 { startY = 0 }
  206. // 结束 Y 值
  207. var endY: CGFloat = startY + power
  208. if endY > CGFloat(hight) { endY = hight }
  209. // 线的路径
  210. let linePath = UIBezierPath()
  211. // 起点
  212. let originX: CGFloat = CGFloat(i * 2)
  213. linePath.move(to: CGPoint(x: originX, y: startY))
  214. // 终点
  215. linePath.addLine(to: CGPoint(x: originX, y: endY))
  216. let lineLayer = CAShapeLayer()
  217. lineLayer.lineWidth = 1
  218. lineLayer.strokeColor = UIColor.white.cgColor
  219. lineLayer.path = linePath.cgPath
  220. lineLayer.fillColor = UIColor.black.cgColor
  221. waveLayers.append(lineLayer)
  222. progressImage.layer.addSublayer(lineLayer)
  223. }
  224. }
  225. @objc func panClick(ges: UIPanGestureRecognizer) {
  226. if ges.state == .changed {
  227. let translation = ges.translation(in: backView)
  228. let totalWidth = progressImage.frame.width + (cutRemindView.frame.width / 2)
  229. let itemWidth = totalWidth / CGFloat(Double("\(bgmData?.duration ?? "1")") ?? 0.0)
  230. let maxX = progressImage.frame.maxX - itemWidth - 3
  231. let minX = cDefaultMargin * 2 - (cutRemindView.frame.width / 2) - 3
  232. let maxWidth = maxX - minX + itemWidth
  233. var gesCenx = (ges.view?.center.x ?? 0) + translation.x
  234. if gesCenx < minX {
  235. gesCenx = minX
  236. } else if gesCenx > maxX {
  237. gesCenx = maxX
  238. }
  239. ges.view?.center.x = gesCenx
  240. currentProgressLab.center.x = panCutBackView.center.x + cDefaultMargin
  241. ges.setTranslation(CGPoint.zero, in: ges.view)
  242. let cenx = (ges.view!.center.x - minX)
  243. currentProgressLab.text = (Float64(cenx / maxWidth) * (Float64("\(bgmData?.duration ?? "0")") ?? 0.0)).formatDurationToHMS()
  244. let startTime = Float64(Int((cenx / maxWidth) * CGFloat(Double("\(bgmData?.duration ?? "0")") ?? 0.0)))
  245. let endTime = Float64(Double("\(bgmData?.duration ?? "0")") ?? 0.0)
  246. BFLog(message: "裁剪背景音乐 duration = \(bgmData?.duration ?? "0"),startTime = \(startTime),endTime = \(endTime)")
  247. bgmData?.currentTime = startTime
  248. avPlayer.seek(to: CMTime(value: CMTimeValue(startTime * playerTimescale), timescale: CMTimeScale(playerTimescale)))
  249. avPlayer.play()
  250. // 重置波纹
  251. resetWave()
  252. }
  253. }
  254. /// 重置波纹
  255. /// - Returns: <#description#>
  256. func resetWave() {
  257. let startTotal = (Float64(waveLayers.count) / (Float64("\(bgmData?.duration ?? "0")") ?? 1)) * Float64(bgmData?.currentTime ?? 0)
  258. if startTotal.isNaN {
  259. return
  260. }
  261. let totalCount = progressImage.layer.sublayers?.count ?? 0
  262. if totalCount > 0 {
  263. for index in 0 ..< Int(totalCount) {
  264. if index < Int(startTotal) {
  265. (progressImage.layer.sublayers?[index] as? CAShapeLayer)?.strokeColor = UIColor.hexColor(hexadecimal: "#202020").cgColor
  266. } else {
  267. (progressImage.layer.sublayers?[index] as? CAShapeLayer)?.strokeColor = UIColor.white.cgColor
  268. }
  269. (progressImage.layer.sublayers?[totalCount - index - 1] as? CAShapeLayer)?.setNeedsDisplay()
  270. }
  271. }
  272. }
  273. func configPlayProgress(currentTime: Float64) {
  274. let totalCount = progressImage.layer.sublayers?.count ?? 0
  275. if currentTime >= 0 {
  276. // 播放的开始点
  277. let startPoint = (Float64(waveLayers.count) / (Float64("\(bgmData?.duration ?? "0")") ?? 1)) * Float64(bgmData?.currentTime ?? 0)
  278. // 已经播放到的点
  279. let endPoint = (Float64(waveLayers.count) / (Float64("\(bgmData?.duration ?? "0")") ?? 1)) * currentTime
  280. if endPoint < startPoint {
  281. BFLog(message: "startPoint: \(startPoint) endPoint:\(endPoint)")
  282. return
  283. }
  284. if totalCount > 0 {
  285. for index in Int(startPoint) ... Int(endPoint) {
  286. var tempIndex = index
  287. if tempIndex < 0 {
  288. tempIndex = 0
  289. }
  290. if tempIndex >= totalCount - 1 {
  291. tempIndex = totalCount - 1
  292. }
  293. (progressImage.layer.sublayers?[tempIndex] as? CAShapeLayer)?.strokeColor = UIColor.hexColor(hexadecimal: "#389AFF").cgColor
  294. (progressImage.layer.sublayers?[tempIndex] as? CAShapeLayer)?.setNeedsDisplay()
  295. }
  296. }
  297. }
  298. }
  299. func show() {
  300. isHidden = false
  301. }
  302. @objc func hidden() {
  303. isHidden = true
  304. avPlayer.pause()
  305. }
  306. @objc func cancelAction() {
  307. hidden()
  308. }
  309. @objc func okBtnAction() {
  310. hidden()
  311. bgmData?.startCMTime = CMTime(value: CMTimeValue((bgmData?.currentTime ?? 0.0) * playerTimescale), timescale: CMTimeScale(playerTimescale))
  312. BFLog(message: "最后设置的开始时间是\(bgmData?.startCMTime.seconds ?? 0.0)")
  313. if cutTimeHandle != nil, bgmData != nil {
  314. cutTimeHandle!(0, 0, bgmData)
  315. }
  316. }
  317. }