BFMusicCutView.swift 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372
  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: ((_ isCancel: Bool, _ 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. addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(okBtnAction)))
  131. addSubview(backView)
  132. backView.snp.makeConstraints { make in
  133. make.right.equalTo(self.snp.right)
  134. make.bottom.equalTo(self.snp.bottom)
  135. make.width.equalTo(cScreenWidth)
  136. make.height.equalTo(220)
  137. }
  138. backView.setNeedsUpdateConstraints()
  139. // 取消
  140. let cancelBtn = UIButton()
  141. cancelBtn.backgroundColor = .clear
  142. cancelBtn.setTitle("取消", for: .normal)
  143. cancelBtn.setTitleColor(.white, for: .normal)
  144. cancelBtn.titleLabel?.font = UIFont.boldSystemFont(ofSize: 17)
  145. cancelBtn.addTarget(self, action: #selector(cancelAction), for: .touchDown)
  146. backView.addSubview(cancelBtn)
  147. cancelBtn.snp.makeConstraints { make in
  148. make.left.equalToSuperview().offset(18)
  149. make.width.equalTo(35)
  150. make.height.equalTo(24)
  151. make.top.equalToSuperview().offset(18)
  152. }
  153. backView.addSubview(titleL)
  154. titleL.snp.makeConstraints { make in
  155. make.centerX.equalToSuperview()
  156. make.width.equalTo(54)
  157. make.height.equalTo(24)
  158. make.top.equalTo(cancelBtn)
  159. }
  160. // 确认
  161. let okBtn = UIButton()
  162. okBtn.backgroundColor = .clear
  163. okBtn.setTitle("确认", for: .normal)
  164. okBtn.setTitleColor(UIColor.hexColor(hexadecimal: "#389AFF"), for: .normal)
  165. okBtn.titleLabel?.font = UIFont.systemFont(ofSize: 17)
  166. okBtn.addTarget(self, action: #selector(okBtnAction), for: .touchUpInside)
  167. backView.addSubview(okBtn)
  168. okBtn.snp.makeConstraints { make in
  169. make.right.equalToSuperview().offset(-18)
  170. make.width.equalTo(35)
  171. make.height.equalTo(24)
  172. make.top.equalToSuperview().offset(18)
  173. }
  174. backView.addSubview(panCutBackView)
  175. panCutBackView.addSubview(cutRemindView)
  176. backView.addSubview(currentProgressLab)
  177. backView.addSubview(progressImage)
  178. if (progressImage.layer.sublayers?.count ?? 0) > 0 {
  179. let totalCount = progressImage.layer.sublayers?.count ?? 0
  180. for index in 0 ..< Int(totalCount) {
  181. (progressImage.layer.sublayers?[index] as? CAShapeLayer)?.strokeColor = UIColor.white.cgColor
  182. (progressImage.layer.sublayers?[index] as? CAShapeLayer)?.setNeedsDisplay()
  183. }
  184. } else {
  185. let totalCount = Int(cScreenWidth - normalMargin * 2) / (cFrequency.count * 2)
  186. let remainder = Int(cScreenWidth - normalMargin * 2) % (cFrequency.count * 2)
  187. var totalWave: [CGFloat] = Array<CGFloat>.init()
  188. for _ in 0 ..< totalCount {
  189. totalWave = totalWave + cFrequency
  190. }
  191. if remainder > 0 {
  192. totalWave = totalWave + cFrequency[0 ... (remainder / 2)]
  193. }
  194. createWave(waveArr: totalWave)
  195. }
  196. }
  197. /// 生成波纹
  198. /// - Parameter waveArr: <#waveArr description#>
  199. /// - Returns: <#description#>
  200. func createWave(waveArr: [CGFloat]) {
  201. for (i, power) in waveArr.enumerated() {
  202. // 画布高度
  203. let hight: CGFloat = progressImage.frame.height
  204. // 开始 Y 值
  205. var startY: CGFloat = (hight - power) / 2.0
  206. if startY < 0 { startY = 0 }
  207. // 结束 Y 值
  208. var endY: CGFloat = startY + power
  209. if endY > CGFloat(hight) { endY = hight }
  210. // 线的路径
  211. let linePath = UIBezierPath()
  212. // 起点
  213. let originX: CGFloat = CGFloat(i * 2)
  214. linePath.move(to: CGPoint(x: originX, y: startY))
  215. // 终点
  216. linePath.addLine(to: CGPoint(x: originX, y: endY))
  217. let lineLayer = CAShapeLayer()
  218. lineLayer.lineWidth = 1
  219. lineLayer.strokeColor = UIColor.white.cgColor
  220. lineLayer.path = linePath.cgPath
  221. lineLayer.fillColor = UIColor.black.cgColor
  222. waveLayers.append(lineLayer)
  223. progressImage.layer.addSublayer(lineLayer)
  224. }
  225. }
  226. @objc func panClick(ges: UIPanGestureRecognizer) {
  227. if ges.state == .changed {
  228. let translation = ges.translation(in: backView)
  229. let totalWidth = progressImage.frame.width + (cutRemindView.frame.width / 2)
  230. let itemWidth = totalWidth / CGFloat(Double("\(bgmData?.duration ?? "1")") ?? 0.0)
  231. let maxX = progressImage.frame.maxX
  232. let minX = cDefaultMargin * 2
  233. let maxWidth = maxX - minX + itemWidth
  234. var gesCenx = (ges.view?.center.x ?? 0) + translation.x
  235. if gesCenx < minX {
  236. gesCenx = minX
  237. } else if gesCenx > maxX {
  238. gesCenx = maxX
  239. }
  240. ges.view?.center.x = gesCenx
  241. currentProgressLab.center.x = panCutBackView.center.x + cDefaultMargin
  242. ges.setTranslation(CGPoint.zero, in: ges.view)
  243. let cenx = (ges.view!.center.x - minX)
  244. currentProgressLab.text = (Float64(cenx / maxWidth) * (Float64("\(bgmData?.duration ?? "0")") ?? 0.0)).formatDurationToHMS()
  245. let startTime = Float64(Int((cenx / maxWidth) * CGFloat(Double("\(bgmData?.duration ?? "0")") ?? 0.0)))
  246. let endTime = Float64(Double("\(bgmData?.duration ?? "0")") ?? 0.0)
  247. BFLog(message: "裁剪背景音乐 duration = \(bgmData?.duration ?? "0"),startTime = \(startTime),endTime = \(endTime)")
  248. bgmData?.currentTime = startTime
  249. avPlayer.seek(to: CMTime(value: CMTimeValue(startTime * playerTimescale), timescale: CMTimeScale(playerTimescale)))
  250. avPlayer.play()
  251. // 重置波纹
  252. resetWave()
  253. }
  254. }
  255. /// 重置波纹
  256. /// - Returns: <#description#>
  257. func resetWave() {
  258. let startTotal = (Float64(waveLayers.count) / (Float64("\(bgmData?.duration ?? "0")") ?? 1)) * Float64(bgmData?.currentTime ?? 0)
  259. if startTotal.isNaN {
  260. return
  261. }
  262. let totalCount = progressImage.layer.sublayers?.count ?? 0
  263. if totalCount > 0 {
  264. for index in 0 ..< Int(totalCount) {
  265. if index < Int(startTotal) {
  266. (progressImage.layer.sublayers?[index] as? CAShapeLayer)?.strokeColor = UIColor.hexColor(hexadecimal: "#202020").cgColor
  267. } else {
  268. (progressImage.layer.sublayers?[index] as? CAShapeLayer)?.strokeColor = UIColor.hexColor(hexadecimal: "#505050").cgColor
  269. }
  270. (progressImage.layer.sublayers?[totalCount - index - 1] as? CAShapeLayer)?.setNeedsDisplay()
  271. }
  272. }
  273. }
  274. func configPlayProgress(currentTime: Float64) {
  275. let totalCount = progressImage.layer.sublayers?.count ?? 0
  276. if currentTime >= 0 {
  277. // 播放的开始点
  278. let startPoint = (Float64(waveLayers.count) / (Float64("\(bgmData?.duration ?? "0")") ?? 1)) * Float64(bgmData?.currentTime ?? 0)
  279. // 已经播放到的点
  280. let endPoint = (Float64(waveLayers.count) / (Float64("\(bgmData?.duration ?? "0")") ?? 1)) * currentTime
  281. if endPoint < startPoint {
  282. BFLog(message: "startPoint: \(startPoint) endPoint:\(endPoint)")
  283. return
  284. }
  285. if totalCount > 0 {
  286. for index in Int(startPoint) ... Int(endPoint) {
  287. var tempIndex = index
  288. if tempIndex < 0 {
  289. tempIndex = 0
  290. }
  291. if tempIndex >= totalCount - 1 {
  292. tempIndex = totalCount - 1
  293. }
  294. (progressImage.layer.sublayers?[tempIndex] as? CAShapeLayer)?.strokeColor = UIColor.hexColor(hexadecimal: "#1B5692").cgColor
  295. (progressImage.layer.sublayers?[tempIndex] as? CAShapeLayer)?.setNeedsDisplay()
  296. }
  297. }
  298. }
  299. }
  300. func show() {
  301. isHidden = false
  302. }
  303. @objc func hidden() {
  304. isHidden = true
  305. avPlayer.pause()
  306. }
  307. @objc func cancelAction() {
  308. hidden()
  309. if cutTimeHandle != nil, bgmData != nil {
  310. cutTimeHandle!(true, 0, bgmData)
  311. }
  312. }
  313. @objc func okBtnAction() {
  314. hidden()
  315. bgmData?.startCMTime = CMTime(value: CMTimeValue((bgmData?.currentTime ?? 0.0) * playerTimescale), timescale: CMTimeScale(playerTimescale))
  316. BFLog(message: "最后设置的开始时间是\(bgmData?.startCMTime.seconds ?? 0.0)")
  317. if cutTimeHandle != nil, bgmData != nil {
  318. cutTimeHandle!(false, 0, bgmData)
  319. }
  320. }
  321. }