|
@@ -0,0 +1,416 @@
|
|
|
+//
|
|
|
+// BFMusicCutView.swift
|
|
|
+// BFRecordScreenKit
|
|
|
+//
|
|
|
+// Created by ak on 2022/3/4.
|
|
|
+// 功能:音乐裁剪界面,设置开始播放位置
|
|
|
+
|
|
|
+import BFCommonKit
|
|
|
+import BFMediaKit
|
|
|
+import BFUIKit
|
|
|
+import Foundation
|
|
|
+import UIKit
|
|
|
+
|
|
|
+// 操作动作类型
|
|
|
+public enum MusiceCutActionType: Int {
|
|
|
+ case MusiceCutActionCancel = 1 // 取消
|
|
|
+ case MusiceCutActionConfirm = 2 // 确认
|
|
|
+
|
|
|
+}
|
|
|
+
|
|
|
+class BFMusicCutView: UIView ,UIGestureRecognizerDelegate {
|
|
|
+ var waveLayers: [CAShapeLayer] = Array<CAShapeLayer>.init()
|
|
|
+ // 裁剪时间回调
|
|
|
+ var cutTimeHandle: ((_ startTime: Float64, _ endTime: Float64, _ bgmData: PQVoiceModel?) -> Void)?
|
|
|
+
|
|
|
+ let normalMargin: CGFloat = cDefaultMargin * 2
|
|
|
+
|
|
|
+ //记录设置的起点, 在点击确认后会设置 bgmdata 的 开始时间为本值。
|
|
|
+ var startCMTime: CMTime = .zero // 开始时间
|
|
|
+
|
|
|
+ //播放音乐
|
|
|
+ lazy var avPlayer: AVPlayer = {
|
|
|
+ let avPlayer = AVPlayer()
|
|
|
+ PQNotification.addObserver(forName: .AVPlayerItemDidPlayToEndTime, object: avPlayer.currentItem, queue: .main) { [weak self] notify in
|
|
|
+ BFLog(message: "AVPlayerItemDidPlayToEndTime = \(notify)")
|
|
|
+ guard let wself = self else { return }
|
|
|
+
|
|
|
+ avPlayer.seek(to: wself.startCMTime)
|
|
|
+ avPlayer.play()
|
|
|
+ wself.configPlayProgress(currentTime: wself.startCMTime.seconds)
|
|
|
+ }
|
|
|
+ PQNotification.addObserver(forName: .AVPlayerItemNewErrorLogEntry, object: avPlayer.currentItem, queue: .main) { notify in
|
|
|
+ BFLog(message: "AVPlayerItemNewErrorLogEntry = \(notify)")
|
|
|
+ }
|
|
|
+ PQNotification.addObserver(forName: .AVPlayerItemFailedToPlayToEndTime, object: avPlayer.currentItem, queue: .main) { notify in
|
|
|
+ BFLog(message: "AVPlayerItemFailedToPlayToEndTime = \(notify)")
|
|
|
+ }
|
|
|
+ PQNotification.addObserver(forName: .AVPlayerItemPlaybackStalled, object: avPlayer.currentItem, queue: .main) { notify in
|
|
|
+ BFLog(message: "AVPlayerItemPlaybackStalled = \(notify)")
|
|
|
+ }
|
|
|
+ avPlayer.addPeriodicTimeObserver(forInterval: CMTime(value: 1, timescale: CMTimeScale(playerTimescale)), queue: .main) { [weak self] _ in
|
|
|
+ guard let wself = self else { return }
|
|
|
+
|
|
|
+ let currentTime = CMTimeGetSeconds(avPlayer.currentItem?.currentTime() ?? CMTime.zero)
|
|
|
+ wself.configPlayProgress(currentTime: currentTime)
|
|
|
+ }
|
|
|
+ avPlayer.volume = 1.0
|
|
|
+ return avPlayer
|
|
|
+ }()
|
|
|
+
|
|
|
+
|
|
|
+ // 总时长
|
|
|
+ lazy var totalTimeLab: UILabel = {
|
|
|
+ let totalTimeLab = UILabel()
|
|
|
+ totalTimeLab.backgroundColor = .red
|
|
|
+ totalTimeLab.textColor = UIColor.white
|
|
|
+ totalTimeLab.font = UIFont.systemFont(ofSize: 13)
|
|
|
+ totalTimeLab.text = "00:00 / 00:00"
|
|
|
+ return totalTimeLab
|
|
|
+ }()
|
|
|
+
|
|
|
+ //放指针的背景图
|
|
|
+ lazy var panCutBackView: UIView = {
|
|
|
+ let panCutBackView = UIView()
|
|
|
+ panCutBackView.backgroundColor = .clear
|
|
|
+ let panGes = UIPanGestureRecognizer(target: self, action: #selector(panClick(ges:)))
|
|
|
+ panCutBackView.addGestureRecognizer(panGes)
|
|
|
+ return panCutBackView
|
|
|
+ }()
|
|
|
+
|
|
|
+ //指针
|
|
|
+ lazy var cutRemindView: UIView = {
|
|
|
+ let cutRemindView = UIView()
|
|
|
+ cutRemindView.backgroundColor = UIColor.hexColor(hexadecimal: "#389AFF")
|
|
|
+ cutRemindView.addCorner(corner: 2)
|
|
|
+ cutRemindView.frame = CGRect(x: (panCutBackView.frame.width - 2) / 2, y: 0, width: 2, height: panCutBackView.frame.height)
|
|
|
+ return cutRemindView
|
|
|
+ }()
|
|
|
+
|
|
|
+ //水波纹的背景
|
|
|
+ lazy var progressImage: UIImageView = {
|
|
|
+ let progressImage = UIImageView()
|
|
|
+ progressImage.frame = CGRect(x: 20, y: currentProgressLab.frame.maxY + 2, width: cScreenWidth - 20 * 2, height: cDefaultMargin * 6)
|
|
|
+ return progressImage
|
|
|
+ }()
|
|
|
+
|
|
|
+ //显示进度
|
|
|
+ lazy var currentProgressLab: UILabel = {
|
|
|
+ let currentProgressLab = UILabel(frame: CGRect(x: cDefaultMargin * 3, y: cDefaultMargin * 6, width: cDefaultMargin * 6, height: cDefaultMargin * 2))
|
|
|
+ currentProgressLab.textColor = UIColor.hexColor(hexadecimal: "#616161")
|
|
|
+ currentProgressLab.font = UIFont.systemFont(ofSize: 13)
|
|
|
+ currentProgressLab.text = "00:00"
|
|
|
+ return currentProgressLab
|
|
|
+ }()
|
|
|
+
|
|
|
+ // 操作板背景
|
|
|
+ let backView = UIButton()
|
|
|
+
|
|
|
+ // 操作回调 isSame 是否点击的是同一个人,进行状态切换使用
|
|
|
+ public var MusiceCutCallBack: ((_ actionType: MusiceCutActionType, _ selectVoice: PQVoiceModel?,_ isSame:Bool ) -> Void)?
|
|
|
+
|
|
|
+ // 当前选择的音乐
|
|
|
+ public var bgmData: PQVoiceModel? {
|
|
|
+ didSet {
|
|
|
+ configPlayProgress(currentTime: bgmData?.currentTime ?? 0)
|
|
|
+ //默认进来播放音乐
|
|
|
+ playBGM()
|
|
|
+ currentProgressLab.center.x = panCutBackView.center.x
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ lazy var titleL : UILabel = {
|
|
|
+ let l = UILabel()
|
|
|
+ l.text = "起播点"
|
|
|
+ l.textAlignment = .center
|
|
|
+ l.textColor = .white
|
|
|
+ l.font = UIFont.systemFont(ofSize: 17, weight: .medium)
|
|
|
+
|
|
|
+ return l
|
|
|
+ }()
|
|
|
+
|
|
|
+ // 删除 btn
|
|
|
+ lazy var deleteBtn: UIButton = {
|
|
|
+ let deleteBtn = UIButton()
|
|
|
+ deleteBtn.backgroundColor = .clear
|
|
|
+ deleteBtn.setTitleColor(.white, for: .normal)
|
|
|
+ deleteBtn.titleLabel?.font = UIFont.systemFont(ofSize: 16)
|
|
|
+ deleteBtn.addTarget(self, action: #selector(deleteBtnAction), for: .touchDown)
|
|
|
+
|
|
|
+ deleteBtn.setBackgroundImage(imageInRecordScreenKit(by: "voiceDeleteN"), for: .normal)
|
|
|
+ deleteBtn.setBackgroundImage(imageInRecordScreenKit(by: "voiceDeleteS"), for: .selected)
|
|
|
+ deleteBtn.adjustsImageWhenHighlighted = false
|
|
|
+ return deleteBtn
|
|
|
+ }()
|
|
|
+
|
|
|
+ required init?(coder _: NSCoder) {
|
|
|
+ fatalError("init(coder:) has not been implemented")
|
|
|
+ }
|
|
|
+
|
|
|
+ override func layoutSubviews() {
|
|
|
+ super.layoutSubviews()
|
|
|
+ backView.addCorner(roundingCorners: [.topLeft, .topRight], corner: 10)
|
|
|
+ addCutViewLayout()
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ func addCutViewLayout() {
|
|
|
+ let totalWidth = (frame.width - normalMargin * 2 - 6)
|
|
|
+ let itemWidth = totalWidth / CGFloat(Double("\(bgmData?.duration ?? "1")") ?? 0.0)
|
|
|
+ panCutBackView.frame = CGRect(x: (normalMargin - cDefaultMargin * 1.5) + CGFloat(bgmData?.startTime ?? 0) * itemWidth, y: currentProgressLab.frame.maxY + 2, width: cDefaultMargin * 3, height: cDefaultMargin * 6)
|
|
|
+ cutRemindView.frame = CGRect(x: (panCutBackView.frame.width - 2) / 2, y: 0, width: 2, height: panCutBackView.frame.height)
|
|
|
+ }
|
|
|
+
|
|
|
+ func playBGM(){
|
|
|
+
|
|
|
+ if(bgmData?.musicPath ?? "").count > 0{
|
|
|
+ avPlayer.pause()
|
|
|
+ avPlayer.replaceCurrentItem(with: AVPlayerItem(url: URL(string: (bgmData?.musicPath ?? ""))!))
|
|
|
+ avPlayer.seek(to: (bgmData?.startCMTime ?? .zero))
|
|
|
+ avPlayer.play()
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ override init(frame: CGRect) {
|
|
|
+ super.init(frame: frame)
|
|
|
+
|
|
|
+ backgroundColor = UIColor(red: 0, green: 0, blue: 0, alpha: 0)
|
|
|
+ backView.backgroundColor = UIColor.hexColor(hexadecimal: "#121212")
|
|
|
+
|
|
|
+ addSubview(backView)
|
|
|
+ backView.snp.makeConstraints { make in
|
|
|
+ make.right.equalTo(self.snp.right)
|
|
|
+ make.bottom.equalTo(self.snp.bottom)
|
|
|
+ make.width.equalTo(cScreenWidth)
|
|
|
+ make.height.equalTo(220)
|
|
|
+ }
|
|
|
+ backView.setNeedsUpdateConstraints()
|
|
|
+
|
|
|
+ // 取消
|
|
|
+ let cancelBtn = UIButton()
|
|
|
+ cancelBtn.backgroundColor = .clear
|
|
|
+ cancelBtn.setTitle("取消", for: .normal)
|
|
|
+ cancelBtn.setTitleColor(.white, for: .normal)
|
|
|
+ cancelBtn.titleLabel?.font = UIFont.boldSystemFont(ofSize: 17)
|
|
|
+ cancelBtn.addTarget(self, action: #selector(cancelAction), for: .touchDown)
|
|
|
+ backView.addSubview(cancelBtn)
|
|
|
+
|
|
|
+ cancelBtn.snp.makeConstraints { make in
|
|
|
+ make.left.equalToSuperview().offset(18)
|
|
|
+ make.width.equalTo(35)
|
|
|
+ make.height.equalTo(24)
|
|
|
+ make.top.equalToSuperview().offset(18)
|
|
|
+ }
|
|
|
+
|
|
|
+ backView.addSubview(titleL)
|
|
|
+ titleL.snp.makeConstraints { make in
|
|
|
+ make.centerX.equalToSuperview()
|
|
|
+ make.width.equalTo(54)
|
|
|
+ make.height.equalTo(24)
|
|
|
+ make.top.equalTo(cancelBtn)
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ // 确认
|
|
|
+ let okBtn = UIButton()
|
|
|
+ okBtn.backgroundColor = .clear
|
|
|
+ okBtn.setTitle("确认", for: .normal)
|
|
|
+ okBtn.setTitleColor(UIColor.hexColor(hexadecimal: "#389AFF"), for: .normal)
|
|
|
+ okBtn.titleLabel?.font = UIFont.systemFont(ofSize: 17)
|
|
|
+ okBtn.addTarget(self, action: #selector(okBtnAction), for: .touchUpInside)
|
|
|
+ backView.addSubview(okBtn)
|
|
|
+ okBtn.snp.makeConstraints { make in
|
|
|
+ make.right.equalToSuperview().offset(-18)
|
|
|
+ make.width.equalTo(35)
|
|
|
+ make.height.equalTo(24)
|
|
|
+ make.top.equalToSuperview().offset(18)
|
|
|
+ }
|
|
|
+
|
|
|
+ backView.addSubview(totalTimeLab)
|
|
|
+ backView.addSubview(panCutBackView)
|
|
|
+ panCutBackView.addSubview(cutRemindView)
|
|
|
+ backView.addSubview(currentProgressLab)
|
|
|
+ backView.addSubview(progressImage)
|
|
|
+
|
|
|
+ if (progressImage.layer.sublayers?.count ?? 0) > 0 {
|
|
|
+ let totalCount = progressImage.layer.sublayers?.count ?? 0
|
|
|
+ for index in 0..<Int(totalCount) {
|
|
|
+ (progressImage.layer.sublayers?[index] as? CAShapeLayer)?.strokeColor = UIColor.white.cgColor
|
|
|
+ (progressImage.layer.sublayers?[index] as? CAShapeLayer)?.setNeedsDisplay()
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ let totalCount = Int(cScreenWidth - normalMargin * 2) / (cFrequency.count * 2)
|
|
|
+ let remainder = Int(cScreenWidth - normalMargin * 2) % (cFrequency.count * 2)
|
|
|
+ var totalWave: [CGFloat] = Array<CGFloat>.init()
|
|
|
+ for _ in 0..<totalCount {
|
|
|
+ totalWave = totalWave + cFrequency
|
|
|
+ }
|
|
|
+ if remainder > 0 {
|
|
|
+ totalWave = totalWave + cFrequency[0...(remainder / 2)]
|
|
|
+ }
|
|
|
+ createWave(waveArr: totalWave)
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ /// 生成波纹
|
|
|
+ /// - Parameter waveArr: <#waveArr description#>
|
|
|
+ /// - Returns: <#description#>
|
|
|
+ func createWave(waveArr: [CGFloat]) {
|
|
|
+ for (i, power) in waveArr.enumerated() {
|
|
|
+ // 画布高度
|
|
|
+ let hight: CGFloat = progressImage.frame.height
|
|
|
+ // 开始 Y 值
|
|
|
+ var startY: CGFloat = (hight - power) / 2.0
|
|
|
+ if startY < 0 { startY = 0 }
|
|
|
+ // 结束 Y 值
|
|
|
+ var endY: CGFloat = startY + power
|
|
|
+ if endY > CGFloat(hight) { endY = hight }
|
|
|
+ // 线的路径
|
|
|
+ let linePath = UIBezierPath()
|
|
|
+
|
|
|
+ // 起点
|
|
|
+ let originX: CGFloat = CGFloat(i * 2)
|
|
|
+ linePath.move(to: CGPoint(x: originX, y: startY))
|
|
|
+ // 终点
|
|
|
+ linePath.addLine(to: CGPoint(x: originX, y: endY))
|
|
|
+
|
|
|
+ let lineLayer = CAShapeLayer()
|
|
|
+
|
|
|
+ lineLayer.lineWidth = 1
|
|
|
+
|
|
|
+ lineLayer.strokeColor = UIColor.white.cgColor
|
|
|
+
|
|
|
+ lineLayer.path = linePath.cgPath
|
|
|
+ lineLayer.fillColor = UIColor.black.cgColor
|
|
|
+
|
|
|
+ waveLayers.append(lineLayer)
|
|
|
+ progressImage.layer.insertSublayer(lineLayer, at: 0)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ @objc func panClick(ges: UIPanGestureRecognizer) {
|
|
|
+ if ges.state == .changed {
|
|
|
+ let translation = ges.translation(in: backView)
|
|
|
+ let totalWidth = progressImage.frame.width + (cutRemindView.frame.width / 2)
|
|
|
+ let itemWidth = totalWidth / CGFloat(Double("\(bgmData?.duration ?? "1")") ?? 0.0)
|
|
|
+ let maxX = progressImage.frame.maxX - itemWidth - 3
|
|
|
+ let minX = cDefaultMargin * 2 - (cutRemindView.frame.width / 2) - 3
|
|
|
+ let maxWidth = maxX - minX + itemWidth
|
|
|
+ var gesCenx = (ges.view?.center.x ?? 0) + translation.x
|
|
|
+ if gesCenx < minX {
|
|
|
+ gesCenx = minX
|
|
|
+ } else if gesCenx > maxX {
|
|
|
+ gesCenx = maxX
|
|
|
+ }
|
|
|
+ ges.view?.center.x = gesCenx
|
|
|
+ currentProgressLab.center.x = panCutBackView.center.x + cDefaultMargin
|
|
|
+ ges.setTranslation(CGPoint.zero, in: ges.view)
|
|
|
+ let cenx = (ges.view!.center.x - minX)
|
|
|
+ currentProgressLab.text = (Float64(cenx / maxWidth) * (Float64("\(bgmData?.duration ?? "0")") ?? 0.0)).formatDurationToHMS()
|
|
|
+ let startTime = Float64(Int((cenx / maxWidth) * CGFloat(Double("\(bgmData?.duration ?? "0")") ?? 0.0)))
|
|
|
+ let endTime = Float64(Double("\(bgmData?.duration ?? "0")") ?? 0.0)
|
|
|
+ BFLog(message: "裁剪背景音乐 duration = \(bgmData?.duration ?? "0"),startTime = \(startTime),endTime = \(endTime)")
|
|
|
+ bgmData?.currentTime = startTime
|
|
|
+ bgmData?.startTime = startTime
|
|
|
+ bgmData?.endTime = endTime
|
|
|
+// if cutTimeHandle != nil {
|
|
|
+// cutTimeHandle!(startTime, endTime, bgmData)
|
|
|
+// }
|
|
|
+
|
|
|
+ avPlayer.seek(to: CMTime(value: CMTimeValue(startTime * playerTimescale), timescale: CMTimeScale(playerTimescale)))
|
|
|
+ // 重置波纹
|
|
|
+ resetWave()
|
|
|
+ }
|
|
|
+ }
|
|
|
+ /// 重置波纹
|
|
|
+ /// - Returns: <#description#>
|
|
|
+ func resetWave() {
|
|
|
+ let startTotal = (Float64(waveLayers.count) / (Float64("\(bgmData?.duration ?? "0")") ?? 1)) * Float64(bgmData?.startTime ?? 0)
|
|
|
+ if startTotal.isNaN {
|
|
|
+ return
|
|
|
+ }
|
|
|
+ let totalCount = progressImage.layer.sublayers?.count ?? 0
|
|
|
+ if totalCount > 0 {
|
|
|
+ for index in 0..<Int(totalCount) {
|
|
|
+ if (totalCount - index - 1) < totalCount {
|
|
|
+ if index < Int(startTotal) {
|
|
|
+ (progressImage.layer.sublayers?[totalCount - index - 1] as? CAShapeLayer)?.strokeColor = UIColor.hexColor(hexadecimal: "#202020").cgColor
|
|
|
+ } else {
|
|
|
+ (progressImage.layer.sublayers?[totalCount - index - 1] as? CAShapeLayer)?.strokeColor = UIColor.white.cgColor
|
|
|
+ }
|
|
|
+ (progressImage.layer.sublayers?[totalCount - index - 1] as? CAShapeLayer)?.setNeedsDisplay()
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ func configPlayProgress(currentTime: Float64) {
|
|
|
+ bgmData?.currentTime = currentTime < 0 ? 0 : currentTime
|
|
|
+ let totalCount = progressImage.layer.sublayers?.count ?? 0
|
|
|
+ // 循环播放时重置
|
|
|
+ if (bgmData?.currentTime ?? 0) == (bgmData?.startTime ?? 0) {
|
|
|
+ // 重置波纹
|
|
|
+ resetWave()
|
|
|
+ }
|
|
|
+ if Float64(bgmData?.currentTime ?? 0) > Float64(bgmData?.startTime ?? 0) {
|
|
|
+ let sdd = (Float64(bgmData?.currentTime ?? 0) - Float64(bgmData?.startTime ?? 0))
|
|
|
+ let total = (Float64(waveLayers.count) / (Float64("\(bgmData?.duration ?? "0")") ?? 1)) * sdd
|
|
|
+ let startTotal = (Float64(waveLayers.count) / (Float64("\(bgmData?.duration ?? "0")") ?? 1)) * Float64(bgmData?.startTime ?? 0)
|
|
|
+
|
|
|
+ if totalCount > 0 {
|
|
|
+ for index in 0..<Int(total) {
|
|
|
+ var tempIndex = (totalCount - index - 1 - Int(startTotal))
|
|
|
+ if tempIndex < 0 {
|
|
|
+ tempIndex = 0
|
|
|
+ }
|
|
|
+ if tempIndex >= totalCount - 1 {
|
|
|
+ tempIndex = totalCount - 1
|
|
|
+ }
|
|
|
+ (progressImage.layer.sublayers?[tempIndex] as? CAShapeLayer)?.strokeColor = UIColor.hexColor(hexadecimal: "#389AFF").cgColor
|
|
|
+ (progressImage.layer.sublayers?[tempIndex] as? CAShapeLayer)?.setNeedsDisplay()
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ func show(){
|
|
|
+ isHidden = false
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ @objc func hidden() {
|
|
|
+ isHidden = true
|
|
|
+ avPlayer.pause()
|
|
|
+ }
|
|
|
+
|
|
|
+ @objc func cancelAction() {
|
|
|
+ hidden()
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ @objc func okBtnAction() {
|
|
|
+ hidden()
|
|
|
+// if MusiceCutCallBack != nil {
|
|
|
+// MusiceCutCallBack!(.MusiceCutActionConfirm, selectVoice,false)
|
|
|
+// }
|
|
|
+ }
|
|
|
+
|
|
|
+ @objc func deleteBtnAction() {
|
|
|
+
|
|
|
+// if MusiceCutCallBack != nil {
|
|
|
+// MusiceCutCallBack!(.MusiceCutActionDelete, selectVoice,false)
|
|
|
+// }
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+}
|
|
|
+
|