// // BFChooseMusicView.swift // BFRecordScreenKit // // Created by 胡志强 on 2022/2/28. // import Foundation import UIKit import BFUIKit import BFMediaKit import BFCommonKit import BFNetRequestKit enum BFChooseMusicViewClickType { 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) backgroundColor = UIColor.black layer.cornerRadius = 10 let cancelBtn = UIButton() cancelBtn.tag = 1001 cancelBtn.frame = CGRect(x: 18, y: 17, width: 35, height: 24) cancelBtn.setTitle("取消", for: .normal) cancelBtn.setTitleColor(UIColor.hexColor(hexadecimal: "#616161"), for: .normal) cancelBtn.titleLabel?.font = UIFont.systemFont(ofSize: 17, weight: .regular) cancelBtn.addTarget(self, action: #selector(btnAction(btn:)), for: .touchUpInside) addSubview(cancelBtn) 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: "#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.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) { player.pause() switch btn.tag{ case 1001: clickBtnAction?(.cancle) 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 } }