瀏覽代碼

选择音乐弹窗开发

harry 3 年之前
父節點
當前提交
22dc0952fc

+ 138 - 0
BFRecordScreenKit/Classes/RecordScreen/Controller/BFMusicSearchController.swift

@@ -0,0 +1,138 @@
+//
+//  BFMusicSearchController.swift
+//  BFRecordScreenKit
+//
+//  Created by Harry on 2022/3/3.
+//
+
+import Foundation
+import BFUIKit
+import BFMediaKit
+import BFCommonKit
+
+class BFMusicSearchController: BFBaseViewController {
+    
+    var choseAction : ((PQVoiceModel?) -> Void)?
+    
+    var loadedTimeRangesObserver : NSKeyValueObservation?
+
+    var chosedCell : BFMuicInfoCell?
+
+    // 试听音乐
+    let player:AVPlayer = {
+        let p = AVPlayer(playerItem: nil)
+        return p
+    }()
+    
+    lazy var musicTb : UITableView = {
+        let tb = UITableView(frame: .zero)
+        tb.delegate = self
+        tb.dataSource = self
+        tb.separatorColor = UIColor.hexColor(hexadecimal: "#272727")
+        tb.register(BFMusicInfoSearchCell.self, forCellReuseIdentifier: "BFMusicInfoSearchCell")
+        tb.tableFooterView = UIView()
+        tb.backgroundColor = .clear
+        return tb
+    }()
+    
+    override func viewDidLoad() {
+        super.viewDidLoad()
+        
+        let closeBtn = UIButton()
+        closeBtn.setImage(imageInRecordScreenKit(by: "search_close"), for: .normal)
+        closeBtn.frame = CGRect(x: 18, y: statusBarHeight + 2, width: 40, height: 40)
+        view.addSubview(closeBtn)
+        
+        let searBar = UISearchBar(frame: CGRect(x: 62, y: statusBarHeight + 4, width: cScreenWidth - 62 - 18, height: 36))
+        searBar.barStyle = .black
+        searBar.prompt = "歌名/歌手名"
+        
+        view.addSubview(searBar)
+        
+        view.addSubview(musicTb)
+        musicTb.snp.makeConstraints { make in
+            make.left.right.bottom.equalToSuperview()
+            make.top.equalTo(searBar.snp.bottom).offset(4)
+        }
+        
+        loadedTimeRangesObserver = player.observe(\AVPlayer.currentItem?.loadedTimeRanges, options: [.new, .initial]) { [weak self] (player, change) in
+            
+            DispatchQueue.main.async {[weak self] in
+                guard let wself = self else { return }
+                
+                guard let ranges = change.newValue as? [CMTimeRange] else { return }
+                if let cell = wself.chosedCell, let totalDur = player.currentItem?.duration, totalDur.isValid,  CMTimeCompare(ranges.first?.duration ?? .zero, totalDur) >= 0{
+                    if (player.currentItem?.asset as? AVURLAsset)?.url.absoluteString ?? "b" == cell.data?.musicPath ?? "a" {
+                        if cell.status == .loading{
+                            cell.status = .playing
+                        }
+                    }
+                }
+//                BFLog(1, message: "开始播放音乐:\(ranges.first?.start.seconds ?? 0), dur:\( ranges.first?.duration.seconds ?? 0), tot:\(player.currentItem?.duration.seconds ?? 0)")
+            }
+        }
+        
+    }
+}
+
+extension BFMusicSearchController : UITableViewDelegate, UITableViewDataSource {
+    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
+        return 4
+    }
+    
+    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
+        let cell = tableView.dequeueReusableCell(withIdentifier: "BFMusicInfoSearchCell")
+        
+        if let cell = cell as? BFMusicInfoSearchCell{
+            cell.useCallback = {[weak self] in
+                guard let wself = self else { return }
+                wself.choseAction?(cell.data)
+                wself.dismiss(animated: true, completion: nil)
+            }
+        }
+        
+        return cell!
+    }
+    
+    func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
+        return 64
+    }
+    
+    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
+        if let cell = tableView.cellForRow(at: indexPath) as? BFMuicInfoCell {
+//
+//            chosedMusic = cell.data
+//            chosedMusic?.isSelected = true
+            cell.changeSelected(true)
+            
+            chosedCell = cell
+            
+            if cell.status == .normal {
+                if let urlStr = cell.data?.musicPath, let url = URL(string: urlStr){
+                    BFLog(1, message: "歌曲地址: \(url)")
+                    if url.absoluteString != (player.currentItem?.asset as? AVURLAsset)?.url.absoluteString ?? "b" {
+                        player.replaceCurrentItem(with: AVPlayerItem(url: url))
+                        cell.status = .loading
+                    }else {
+                        cell.status = .playing
+                    }
+                    player.play()
+                }
+            }else if cell.status == .pause{
+                player.play()
+            } else{
+                player.pause()
+                cell.status = .pause
+            }
+        }
+    }
+    
+    func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) {
+        let cell = tableView.cellForRow(at: indexPath) as? BFMuicInfoCell
+        cell?.status = .normal
+        cell?.changeSelected(false)
+        
+        chosedCell = nil
+        player.pause()
+    }
+}

+ 44 - 11
BFRecordScreenKit/Classes/RecordScreen/Controller/BFRecordScreenController.swift

@@ -488,7 +488,7 @@ public class BFRecordScreenController: BFBaseViewController {
     // 添加音乐设置
     lazy var addMusicBtn: UIButton = {
         let btn = UIButton(type: .custom)
-        btn.backgroundColor = UIColor.black
+        btn.backgroundColor = UIColor.init(white: 0, alpha: 0.6)
         btn.layer.cornerRadius = 16
         btn.setImage(imageInRecordScreenKit(by: "addmusic"), for: .normal)
         btn.setTitle("选择音乐", for: .normal)
@@ -499,17 +499,56 @@ public class BFRecordScreenController: BFBaseViewController {
     
     lazy var choseMusicPanel: BFChooseMusicView = {
         let vv = BFChooseMusicView(frame: CGRect(x: 0, y: cScreenHeigth, width: cScreenWidth, height: cScreenHeigth - 200))
-        vv.clickBtnAction = { [weak self] type in
+        vv.clickBtnAction = { [weak self, weak vv] type in
             guard let wself = self else { return }
             
+            var nextAction : ((Bool) -> Void)?
             if type == BFChooseMusicViewClickType.sure {
+                let model = vv?.chosedMusic
+                if let title = model?.musicName {
+                    wself.addMusicBtn.setTitle(model?.musicName, for: .normal)
+                }else{
+                    wself.addMusicBtn.setTitle("选择音乐", for: .normal)
+                }
+                // 设置music为背景音乐
+            }else if type == .cancle{
                 
-            }else {
-                
+            }else if type == .search{
+                nextAction = {[weak self] isFinished in
+                    guard let wself = self else { return }
+                    
+                    if isFinished {
+                        let vc = BFMusicSearchController()
+                        vc.modalPresentationStyle = .fullScreen
+                        vc.choseAction = {[weak self] voiceModel in
+                            guard let wself = self else { return }
+                            
+//                            wself.dismiss(animated: true) {
+//                                
+//                            }
+                        }
+                        wself.present(vc, animated: true, completion: nil)
+                    }
+                }
             }
-            UIView.animate(withDuration: 0.25) {
+            
+            let hiddAnimation = {[weak self] in
+                guard let wself = self else { return }
+                
                 wself.choseMusicPanel.frame = CGRect(x: 0, y: cScreenHeigth, width: cScreenWidth, height: cScreenHeigth - 200)
+                
+                wself.soundSettingBtn.isHidden = false
+                wself.cameraFlipBtn.isHidden = false
+                wself.voiceSettingBtn.isHidden = false
+                wself.subtitleBtn.isHidden = false
+                wself.addMusicBtn.isHidden = false
+                
             }
+            
+            UIView.animate(withDuration: 0.25, animations: hiddAnimation, completion: nextAction)
+            
+            
+
         }
         return vv
     }()
@@ -1471,7 +1510,6 @@ public class BFRecordScreenController: BFBaseViewController {
     }
     
     @objc func addMusicAction() {
-        cShowHUB(superView: nil, msg: "选择音乐")
         
         soundSettingBtn.isHidden = true
         cameraFlipBtn.isHidden = true
@@ -1481,11 +1519,6 @@ public class BFRecordScreenController: BFBaseViewController {
         
         UIView.animate(withDuration: 0.25) {[weak self] in
             guard let wself = self else { return }
-            wself.soundSettingBtn.isHidden = false
-            wself.cameraFlipBtn.isHidden = false
-            wself.voiceSettingBtn.isHidden = false
-            wself.subtitleBtn.isHidden = false
-            wself.addMusicBtn.isHidden = false
             
             wself.choseMusicPanel.frame = CGRect(x: 0, y: 210, width: cScreenWidth, height: cScreenHeigth - 200)
         }

+ 366 - 6
BFRecordScreenKit/Classes/RecordScreen/View/BFChooseMusicView.swift

@@ -6,15 +6,96 @@
 //
 
 import Foundation
+import UIKit
+import BFUIKit
+import BFMediaKit
+import BFCommonKit
+import BFNetRequestKit
+
 
 enum BFChooseMusicViewClickType {
-    case sure, cancle
+    case sure, cancle, search
 }
 
+
 class BFChooseMusicView: UIView {
     
     var clickBtnAction: ((BFChooseMusicViewClickType) -> Void)?
     
+    // 各类别音乐列表,以类别id为key
+    var musicArray = [Int64 : [PQVoiceModel]]()
+    
+    // 选择的音乐类别
+    var categorySelectIndex : Int = 1
+    
+    // 音乐类别
+    var categories = [PQStuckPointMusicTagsModel]()
+    
+    // 选中的音乐
+    var chosedMusic : PQVoiceModel?
+    
+    var chosedCell : BFMuicInfoCell?
+    
+    var loadedTimeRangesObserver : NSKeyValueObservation?
+
+    // 试听音乐
+    let player:AVPlayer = {
+        let p = AVPlayer(playerItem: nil)
+        return p
+    }()
+    
+    lazy var categoryView : UICollectionView = {
+        let layout = BFCollectionViewFlowLayout()
+        layout.dele = self
+        layout.scrollDirection = .horizontal
+        layout.minimumLineSpacing = 25
+//        layout.sectionInset = UIEdgeInsets(top: 0, left: 10, bottom: 0, right: 10)
+        layout.minimumInteritemSpacing = 2
+        let vv = UICollectionView(frame: CGRect(x: 60, y: 0, width: cScreenWidth - 60, height: 40), collectionViewLayout: layout)
+        vv.isPagingEnabled = false
+        vv.showsHorizontalScrollIndicator = false
+        vv.showsVerticalScrollIndicator = false
+        vv.register(BFMusicCategoryCell.self, forCellWithReuseIdentifier: "BFMusicCategoryCell")
+        vv.backgroundColor = UIColor.clear
+
+        vv.delegate = self
+        vv.dataSource = self
+        if #available(iOS 11.0, *) {
+            vv.contentInsetAdjustmentBehavior = .never
+        } else {
+            // Fallback on earlier versions
+        }
+        vv.backgroundColor = .clear
+
+        return vv
+    }()
+    
+    lazy var progressL : UILabel = {
+        let l = UILabel()
+        l.textColor = UIColor.hexColor(hexadecimal: "#9d9d9d")
+        l.font = UIFont.systemFont(ofSize: 13, weight: .regular)
+        l.textAlignment = .center
+        l.text = "0%"
+        return l
+    }()
+    
+    lazy var slidV : UISlider = {
+        let v = UISlider()
+        v.addTarget(self, action: #selector(valuChange(slid:)), for: .valueChanged)
+        return v
+    }()
+    
+    lazy var musicTb : UITableView = {
+        let tb = UITableView(frame: .zero)
+        tb.delegate = self
+        tb.dataSource = self
+        tb.separatorColor = UIColor.hexColor(hexadecimal: "#272727")
+        tb.register(BFMuicInfoCell.self, forCellReuseIdentifier: "BFMuicInfoCell")
+        tb.tableFooterView = UIView()
+        tb.backgroundColor = .clear
+        return tb
+    }()
+    
     override init(frame: CGRect) {
         super.init(frame: frame)
         
@@ -33,25 +114,304 @@ class BFChooseMusicView: UIView {
         let sureBtn = UIButton()
         sureBtn.tag = 1002
         sureBtn.frame = CGRect(x: width - 35 - 18, y: 17, width: 35, height: 24)
-        sureBtn.setTitle("取消", for: .normal)
-        sureBtn.setTitleColor(UIColor.hexColor(hexadecimal: "#616161"), for: .normal)
+        sureBtn.setTitle("确定", for: .normal)
+        sureBtn.setTitleColor(UIColor.hexColor(hexadecimal: "#389AFF"), for: .normal)
         sureBtn.titleLabel?.font = UIFont.systemFont(ofSize: 17, weight: .regular)
         sureBtn.addTarget(self, action: #selector(btnAction(btn:)), for: .touchUpInside)
         addSubview(sureBtn)
         
+        let line1 = UIView()
+        line1.backgroundColor = UIColor.hexColor(hexadecimal: "#272727")
+        line1.frame = CGRect(x: 0, y: 50, width: width, height: 1)
+        addSubview(line1)
+
+        categoryView.frame = CGRect(x: 0, y: line1.bottomY, width: width - 50, height: 40)
+        addSubview(categoryView)
+        
+        let searchBtn = UIButton()
+        searchBtn.tag = 1003
+        searchBtn.frame  = CGRect(x: categoryView.rightX+2, y: line1.bottomY, width: 40, height: 40)
+        searchBtn.setImage(imageInRecordScreenKit(by: "search"), for: .normal)
+        searchBtn.addTarget(self, action: #selector(btnAction(btn:)), for: .touchUpInside)
+        addSubview(searchBtn)
+        
+        let line2 = UIView()
+        line2.backgroundColor = UIColor.hexColor(hexadecimal: "#272727")
+        line2.frame = CGRect(x: 0, y: categoryView.bottomY, width: width, height: 1)
+        addSubview(line2)
+        
+        musicTb.frame = CGRect(x: 0, y: line2.bottomY, width: width, height: height - line2.bottomY - 100)
+        addSubview(musicTb)
+        
+        let soundIV = UIImageView(image: imageInRecordScreenKit(by: "soundBtn"))
+        soundIV.frame = CGRect(x: 16, y: musicTb.bottomY + 36, width: 28, height: 28)
+        soundIV.contentMode = .scaleAspectFit
+        addSubview(soundIV)
+        
+        slidV.frame = CGRect(x: soundIV.rightX + 20, y: soundIV.y+5, width: cScreenWidth - soundIV.rightX - 20 - 18, height: 18)
+        addSubview(slidV)
+        
+        progressL.frame = CGRect(x: slidV.x - 7, y: slidV.y - 24, width: 36, height: 16)
+        addSubview(progressL)
+        
+        loadedTimeRangesObserver = player.observe(\AVPlayer.currentItem?.loadedTimeRanges, options: [.new, .initial]) { [weak self] (player, change) in
+            
+            DispatchQueue.main.async {[weak self] in
+                guard let wself = self else { return }
+                
+                guard let ranges = change.newValue as? [CMTimeRange] else { return }
+                if let cell = wself.chosedCell, let totalDur = player.currentItem?.duration, totalDur.isValid,  CMTimeCompare(ranges.first?.duration ?? .zero, totalDur) >= 0{
+                    if (player.currentItem?.asset as? AVURLAsset)?.url.absoluteString ?? "b" == cell.data?.musicPath ?? "a" {
+                        if cell.status == .loading{
+                            cell.status = .playing
+                        }
+                    }
+                }
+                BFLog(1, message: "开始播放音乐:\(ranges.first?.start.seconds ?? 0), dur:\( ranges.first?.duration.seconds ?? 0), tot:\(player.currentItem?.duration.seconds ?? 0)")
+            }
+        }
+        
+        prepareData()
+        
     }
     
     required init?(coder: NSCoder) {
         fatalError("init(coder:) has not been implemented")
     }
     
+    func prepareData() {
+        BFNetRequestAdaptor.postRequestData(url: PQENVUtil.shared.longvideoapi + stuckPointMusicCategoryUrl, parames: ["parentTagId": 0], commonParams: commonParams(), isJsonEncodingNormal: true, timeoutInterval: 15) {[weak self] response, _, error, _ in
+            guard let wself = self else { return }
+            
+            var tagsList = Array<PQStuckPointMusicTagsModel>.init()
+            if response is NSNull || response == nil {
+                cShowHUB(superView: nil, msg: "音乐分类获取失败")
+                return
+            }
+            let tagsTempArr = response as? [[String: Any]]
+            if tagsTempArr != nil, (tagsTempArr?.count ?? 0) > 0 {  
+                for dict in tagsTempArr! {
+                    let tempMusic = PQStuckPointMusicTagsModel(jsonDict: dict)
+                    tempMusic.parentTagId = 0
+                    tagsList.append(tempMusic)
+                }
+            }
+            tagsList.first?.isSelected = true
+            
+            let tempMusic = PQStuckPointMusicTagsModel()
+            tempMusic.tagId = -1
+            tagsList.insert(tempMusic, at: 0)
+            wself.categories.append(contentsOf: tagsList)
+            wself.categoryView.reloadData()
+            if wself.categories.count > 1 {
+                wself.categorySelectIndex = 1
+                if let firstTagid = wself.categories[1].tagId{
+                    wself.getMusicsForCategory(tagId:firstTagid, pageNum: 1)
+                }
+            }
+        }
+    }
+    
+    func getMusicsForCategory(tagId:Int64, pageNum:Int64) {
+        BFNetRequestAdaptor.postRequestData(url: PQENVUtil.shared.longvideoapi + stuckPointMusicPageUrl, parames: ["tagId": tagId, "parentTagId": 0, "pageNum": pageNum, "pageSize": 20], commonParams: commonParams()) {[weak self] response, _, error, _ in
+            guard let wself = self else { return }
+            
+            var musicPageList = [PQVoiceModel]()
+            if response is NSNull || response == nil {
+                cShowHUB(superView: nil, msg: "该分类下无音乐")
+                return
+            }
+            
+            
+            if wself.musicArray[tagId] == nil {
+                wself.musicArray[tagId] = [PQVoiceModel]()
+            }
+            let oldDataMusic : [PQVoiceModel] = wself.musicArray[tagId]!
+            
+            if let tempArr = response as? [[String: Any]], tempArr.count > 0 {
+                for (_, dict) in tempArr.enumerated() {
+                    let tempMusic = PQVoiceModel(jsonDict: dict)
+                    tempMusic.cacheTagID = tagId
+                    if tempMusic.endTime <= tempMusic.startTime {
+                        tempMusic.endTime = tempMusic.startTime + 40
+                    }
+
+                    let haveIndex = oldDataMusic.firstIndex(where: { (music) -> Bool in
+                        music.musicId == tempMusic.musicId
+                    })
+                    if haveIndex == nil {
+                        musicPageList.append(tempMusic)
+                    }
+                }
+            }
+            
+            if musicPageList.count > 0 {
+                wself.musicArray[tagId]!.append(contentsOf: musicPageList)
+                if tagId == wself.categories[wself.categorySelectIndex].tagId{
+                    wself.musicTb.reloadData()
+                }
+            }
+            
+        }
+    }
+    
+    @objc func valuChange(slid:UISlider) {
+        let progress = slid.value
+        let num = (Int)(progress * 100)
+        progressL.text = String(format: "%d%%", num)
+        var frame = progressL.frame
+        frame.origin.x = slidV.x - 7 + ((slidV.width - 28) / 100.0) * CGFloat(num)
+        progressL.frame = frame
+    }
+    
+    func configCategoriesView(){
+        categoryView.reloadData()
+    }
+    
     
     @objc func btnAction(btn:UIButton) {
-        if btn.tag == 1001 {
-            //取消
+        
+        player.pause()
+        
+        switch btn.tag{
+        case 1001:
             clickBtnAction?(.cancle)
-        }else {
+        case 1002:
             clickBtnAction?(.sure)
+        case 1003:
+            clickBtnAction?(.search)
+        default:break
         }
     }
+    
+    func reloadView() {
+        
+    }
+    
+}
+
+extension BFChooseMusicView:UITableViewDelegate, UITableViewDataSource {
+    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
+        let cell = tableView.dequeueReusableCell(withIdentifier: "BFMuicInfoCell", for: indexPath) as! BFMuicInfoCell
+        cell.selectionStyle = .none
+        cell.cutCallBack = {
+            // 调用裁剪UIView
+        }
+        if let tagid = categories[categorySelectIndex].tagId, let arr = musicArray[tagid] {
+            cell.data = arr[indexPath.row]
+        }
+        
+        return cell
+    }
+    
+    func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
+        return 64
+    }
+    
+    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
+        if categories.count > 1 {
+            return musicArray[categories[categorySelectIndex].tagId ?? 0]?.count ?? 0
+        }
+        return 0
+    }
+    
+    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
+        if let cell = tableView.cellForRow(at: indexPath) as? BFMuicInfoCell {
+            
+            chosedMusic = cell.data
+            chosedMusic?.isSelected = true
+            cell.changeSelected(true)
+            
+            chosedCell = cell
+            
+            if cell.status == .normal {
+                if let urlStr = cell.data?.musicPath, let url = URL(string: urlStr){
+                    BFLog(1, message: "歌曲地址: \(url)")
+                    if url.absoluteString != (player.currentItem?.asset as? AVURLAsset)?.url.absoluteString ?? "b" {
+                        player.replaceCurrentItem(with: AVPlayerItem(url: url))
+                        cell.status = .loading
+                    }else {
+                        cell.status = .playing
+                    }
+                    player.play()
+                }
+            }else if cell.status == .pause{
+                player.play()
+            } else{
+                player.pause()
+                cell.status = .pause
+            }
+        }
+    }
+    
+    func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) {
+        let cell = tableView.cellForRow(at: indexPath) as? BFMuicInfoCell
+        cell?.status = .normal
+        cell?.changeSelected(false)
+        
+        chosedCell = nil
+        player.pause()
+    }
+}
+
+extension BFChooseMusicView : UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout{
+    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
+        return categories.count
+    }
+    
+    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
+        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "BFMusicCategoryCell", for: indexPath) as! BFMusicCategoryCell
+        cell.addData(dic: categories[indexPath.row])
+        cell.isChoosed = (indexPath.row == categorySelectIndex)
+     
+        return cell
+    }
+    
+    func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
+        let oldCell = collectionView.cellForItem(at: IndexPath(row: categorySelectIndex, section: 0)) as? BFMusicCategoryCell
+        let newCell = collectionView.cellForItem(at: indexPath) as? BFMusicCategoryCell
+        if indexPath.row > 0 {
+            categorySelectIndex = indexPath.row
+            musicTb.reloadData()
+            
+            oldCell?.isChoosed = false
+            newCell?.isChoosed = true
+            
+            getMusicsForCategory(tagId: categories[categorySelectIndex].tagId ?? 0, pageNum: 1)
+            
+        }else {
+            // 取消选歌
+            chosedMusic = nil
+        }
+    }
+    
 }
+
+extension BFChooseMusicView: BFFlowLayoutDelegate{
+    
+    func flowLayout(_ flowLayout: BFCollectionViewFlowLayout, itemHeight indexPath: IndexPath) -> CGFloat {
+        
+        var itemWidth = 30.0
+        
+        if let title = categories[indexPath.row].tagName, title.count > 0{
+            let ww = title.textAutoWidth(height: 20, font: UIFont.systemFont(ofSize: 15, weight: .regular))
+            itemWidth = max(30.0, ww)
+        }
+        
+        return itemWidth
+    }
+}
+
+extension String{
+    func textAutoWidth(height:CGFloat, font:UIFont) ->CGFloat{
+        let string = self as NSString
+        let origin = NSStringDrawingOptions.usesLineFragmentOrigin
+        let lead = NSStringDrawingOptions.usesFontLeading
+        let rect = string.boundingRect(with:CGSize(width:0, height: height), options: [origin,lead],
+                                       attributes: [NSAttributedString.Key.font:font],
+                                       context:nil)
+        return rect.width
+        
+    }
+}
+

+ 61 - 0
BFRecordScreenKit/Classes/RecordScreen/View/Cell/BFCollectionViewFlowLayout.swift

@@ -0,0 +1,61 @@
+//
+//  BFCollectionViewFlowLayout.swift
+//  BFRecordScreenKit
+//
+//  Created by 胡志强 on 2022/3/1.
+//
+
+import Foundation
+
+
+protocol BFFlowLayoutDelegate: NSObjectProtocol {
+    func flowLayout(_ flowLayout: BFCollectionViewFlowLayout, itemHeight indexPath: IndexPath) -> CGFloat
+}
+
+class BFCollectionViewFlowLayout: UICollectionViewFlowLayout {
+    weak var dele : BFFlowLayoutDelegate?
+    var totalWidth = 0.0
+
+    var layoutAttributeArray = [UICollectionViewLayoutAttributes]()
+    
+    override func prepare() {
+        super.prepare()
+        // 计算每个 Cell 的宽度
+        let itemHeight = 40.0
+        // Cell 数量
+        let itemCount = collectionView!.numberOfItems(inSection: 0)
+
+        // 遍历 item 计算并缓存属性
+        totalWidth = 0.0
+        
+        for i in layoutAttributeArray.count ..< itemCount {
+            let indexPath = IndexPath(item: i, section: 0)
+            
+            let attr = UICollectionViewLayoutAttributes(forCellWith: indexPath)
+            
+            // 获取动态高度
+            let itemWidth = dele?.flowLayout(self, itemHeight: indexPath) ?? 0.0
+
+            // 赋值新的位置信息
+            attr.frame = CGRect(x: totalWidth, y: 0, width: itemWidth + minimumLineSpacing, height: CGFloat(itemHeight))
+            // 缓存布局属性
+            layoutAttributeArray.append(attr)
+            totalWidth = attr.frame.maxX
+        }
+    }
+    
+    
+}
+
+extension BFCollectionViewFlowLayout {
+    
+    override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
+        return layoutAttributeArray.filter {
+            $0.frame.intersects(rect)
+        }
+    }
+    
+    override var collectionViewContentSize: CGSize {
+        return CGSize(width: totalWidth , height: collectionView!.bounds.height)
+    }
+}

+ 179 - 0
BFRecordScreenKit/Classes/RecordScreen/View/Cell/BFMuicInfoCell.swift

@@ -0,0 +1,179 @@
+//
+//  BFMuicInfoCell.swift
+//  BFRecordScreenKit
+//
+//  Created by 胡志强 on 2022/3/1.
+//
+
+import Foundation
+import BFMediaKit
+import BFCommonKit
+import BFMaterialKit
+
+enum BFMuicInfoCellState {
+    case normal, playing, pause, loading
+}
+
+class BFMuicInfoCell: UITableViewCell {
+    var cutCallBack : (() -> Void)?
+    
+    var data:PQVoiceModel?{
+        didSet{
+            addData()
+        }
+    }
+
+    
+    var status : BFMuicInfoCellState = .normal {
+        didSet {
+            switch status {
+            case .normal:
+                maskIV.image = nil
+                audioStateView.isHidden = true
+
+            case .playing:
+                maskIV.image = imageInRecordScreenKit(by: "markPlaying")
+                stopLoading()
+                audioStateView.isHidden = false
+                audioStateView.kf.setImage(with: URL(fileURLWithPath: currentBundle()!.path(forResource: "stuckPoint_music_playing", ofType: ".gif")!))
+                
+            case .pause:
+                maskIV.image = imageInRecordScreenKit(by: "markSelectd")
+                stopLoading()
+                audioStateView.isHidden = false
+                audioStateView.image = imageInRecordScreenKit(by: "stuckPoint_music_pause")
+
+            case .loading:
+                maskIV.image = imageInRecordScreenKit(by: "markSelectd")
+                audioStateView.image = imageInRecordScreenKit(by: "audioLoding")?.withRenderingMode(.alwaysTemplate)
+                loadingAnimation()
+                audioStateView.isHidden = false
+            }
+        }
+    }
+    
+    lazy var iconIV : UIImageView = {
+        let iv = UIImageView()
+        iv.layer.cornerRadius = 21
+        iv.layer.masksToBounds = true
+        
+        return iv
+    }()
+    
+    lazy var maskIV : UIImageView = {
+        let iv = UIImageView()
+        iv.layer.masksToBounds = true
+        iv.layer.borderColor = UIColor.hexColor(hexadecimal: "#389AFF").cgColor
+        iv.layer.cornerRadius = 21
+        iv.layer.borderWidth = 2
+        iv.layer.masksToBounds = true
+        iv.backgroundColor = UIColor.init(white: 0, alpha: 0.5)
+        
+        return iv
+    }()
+
+    // 语音播放动画和暂停播放
+    lazy var audioStateView: UIImageView = {
+        let iv = UIImageView()
+        iv.image = imageInRecordScreenKit(by: "audioLoding")
+        iv.contentMode = .scaleAspectFit
+        iv.isHidden = true
+        return iv
+    }()
+    
+    lazy var titleL : UILabel = {
+        let l = UILabel()
+        l.text = "歌曲"
+        l.textColor = .white
+        l.font = UIFont.systemFont(ofSize: 16, weight: .regular)
+        l.textAlignment = .left
+        return l
+    }()
+    
+    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
+        super.init(style: style, reuseIdentifier: reuseIdentifier)
+        self.backgroundColor = .clear
+        contentView.backgroundColor = .clear
+        
+        iconIV.frame = CGRect(x: 18, y: 11, width: 42, height: 42)
+        maskIV.frame = iconIV.bounds
+        audioStateView.frame = CGRect(x: 8, y: 8, width: 26, height: 26)
+        
+        iconIV.addSubview(maskIV)
+        iconIV.addSubview(audioStateView)
+        contentView.addSubview(iconIV)
+        
+        titleL.frame = CGRect(x: iconIV.rightX + 10, y: 12, width: width - iconIV.rightX - 10 - 90, height: 40)
+        contentView.addSubview(titleL)
+
+    }
+    
+    required init?(coder: NSCoder) {
+        fatalError("init(coder:) has not been implemented")
+    }
+    
+    func addData() {
+        if let data = data{
+            iconIV.setNetImage(url: data.avatarUrl, placeholder: imageInCommon(by: "videomk_music_default")!)
+            titleL.text = data.musicName
+        }
+    }
+    
+    func changeSelected(_ selected: Bool){
+        if selected{
+            titleL.textColor = UIColor.hexColor(hexadecimal: "#389AFF")
+            showFunctionBtn()
+        }else {
+            titleL.textColor = UIColor.white
+            hiddenFunctionBtn()
+        }
+    }
+    
+    func createCutBtn() -> UIButton {
+        let cutBtn = UIButton()
+        cutBtn.tag = 2001
+        cutBtn.setImage(imageInRecordScreenKit(by: "clip"), for: .normal)
+        cutBtn.addTarget(self, action: #selector(cutAction), for: .touchUpInside)
+        return cutBtn
+    }
+    
+    func showFunctionBtn() {
+        let cutBtn = createCutBtn()
+        contentView.addSubview(cutBtn)
+        cutBtn.snp.makeConstraints { make in
+            make.right.equalTo(-18)
+            make.width.height.equalTo(40)
+            make.top.equalTo(12)
+        }
+    }
+    
+    func hiddenFunctionBtn() {
+        if let v = contentView.viewWithTag(2001) {
+            v.removeFromSuperview()
+        }
+    }
+    
+    @objc func cutAction() {
+        cutCallBack?()
+    }
+    
+    func loadingAnimation(){
+        let rotationAnim = CABasicAnimation(keyPath: "transform.rotation.z")
+        // 2.设置动画的属性
+        rotationAnim.fromValue = 0
+        rotationAnim.toValue = Double.pi * 2
+        rotationAnim.repeatCount = MAXFLOAT
+        rotationAnim.duration = 1
+        // 这个属性很重要 如果不设置当页面运行到后台再次进入该页面的时候 动画会停止
+        rotationAnim.isRemovedOnCompletion = false
+        // 3.将动画添加到layer中
+        audioStateView.layer.add(rotationAnim, forKey: nil)
+
+    }
+    
+    func stopLoading(){
+        audioStateView.layer.removeAllAnimations()
+    }
+    
+    
+}

+ 100 - 0
BFRecordScreenKit/Classes/RecordScreen/View/Cell/BFMusicCategoryCell.swift

@@ -0,0 +1,100 @@
+//
+//  BFMusicCategoryCell.swift
+//  BFRecordScreenKit
+//
+//  Created by 胡志强 on 2022/3/1.
+//
+
+import Foundation
+import BFMediaKit
+
+class BFMusicCategoryCell: UICollectionViewCell {
+    
+    var isChoosed : Bool = false {
+        didSet{
+            if titleL.text?.count ?? 0 > 0{
+                if isChoosed {
+                    titleL.textColor = UIColor.white
+                    line.isHidden = false
+                }else {
+                    titleL.textColor = UIColor.hexColor(hexadecimal: "#616161")
+                    line.isHidden = true
+                }
+            }
+            
+        }
+    }
+    lazy var titleL : UILabel = {
+        let l = UILabel()
+        l.textColor = UIColor.white
+        l.font = UIFont.systemFont(ofSize: 15, weight: .regular)
+        l.textColor = UIColor.hexColor(hexadecimal: "#616161")
+        l.textAlignment = .center
+        
+        return l
+    }()
+    
+    lazy var icon : UIImageView = {
+        let iv = UIImageView()
+        iv.contentMode = .scaleAspectFit
+        
+        return iv
+    }()
+    
+    lazy var line : UIView = {
+        let v = UIView()
+        v.backgroundColor = .white
+        v.isHidden = true
+        return v
+    }()
+    
+    override init(frame: CGRect) {
+        super.init(frame: frame)
+        backgroundColor = UIColor.clear
+ 
+        contentView.addSubview(titleL)
+        titleL.snp.makeConstraints { make in
+            make.left.right.top.equalToSuperview()
+            make.bottom.equalTo(-8)
+        }
+        
+        contentView.addSubview(line)
+        line.snp.makeConstraints { make in
+            make.left.equalTo(3)
+            make.right.equalTo(-3)
+            make.height.equalTo(2)
+            make.bottom.equalToSuperview()
+        }
+        
+        contentView.addSubview(icon)
+        icon.snp.makeConstraints { make in
+            make.center.equalToSuperview()
+            make.width.height.equalTo(24)
+        }
+        
+        
+//
+//        contentView.addSubview(line1);
+ 
+    }
+
+    required init?(coder _: NSCoder) {
+        fatalError("init(coder:) has not been implemented")
+    }
+    
+    func addData(dic:PQStuckPointMusicTagsModel) {
+        icon.isHidden = true
+        titleL.isHidden = true
+        if let title = dic.tagName, title.count > 0 {
+            titleL.isHidden = false
+            titleL.text = title
+            icon.image = nil
+        }else if let tagid = dic.tagId, tagid == -1 {
+            icon.isHidden = false
+            titleL.text = ""
+            icon.image = imageInRecordScreenKit(by: "voiceDeleteS")
+        }
+    }
+
+    
+}

+ 54 - 0
BFRecordScreenKit/Classes/RecordScreen/View/Cell/BFMusicInfoSearchCell.swift

@@ -0,0 +1,54 @@
+//
+//  BFMusicInfoSearchCell.swift
+//  BFRecordScreenKit
+//
+//  Created by Harry on 2022/3/3.
+//
+
+import Foundation
+
+
+class BFMusicInfoSearchCell: BFMuicInfoCell {
+    
+    var useCallback : (() -> Void)?
+    
+    override func showFunctionBtn() {
+        
+        let useBtn = createCutBtn()
+        contentView.addSubview(useBtn)
+        useBtn.snp.makeConstraints { make in
+            make.right.equalTo(-18)
+            make.width.height.equalTo(40)
+            make.top.equalTo(1)
+        }
+        
+        let cutBtn = createCutBtn()
+        contentView.addSubview(cutBtn)
+        cutBtn.snp.makeConstraints { make in
+            make.right.equalTo(useBtn.snp.left).offset(-10)
+            make.width.height.equalTo(40)
+            make.top.equalTo(1)
+        }
+    }
+    
+    override func hiddenFunctionBtn() {
+        super.hiddenFunctionBtn()
+        if let v = contentView.viewWithTag(2002){
+            v.removeFromSuperview()
+        }
+    }
+    
+    func createUseBtn() -> UIButton {
+        let useBtn = UIButton()
+        useBtn.tag = 2002
+        useBtn.setTitle("使用", for: .normal)
+        useBtn.backgroundColor = UIColor.hexColor(hexadecimal: "#389AFF")
+        useBtn.layer.cornerRadius = 4
+        useBtn.addTarget(self, action: #selector(useBtnAction), for: .touchUpInside)
+        return useBtn
+    }
+    
+    @objc func useBtnAction() {
+        useCallback?()
+    }
+}