杨锴
2024-09-12 e15c976316feef72ff9bcabce38e0a078f9505db
XQMuse/Root/PayMusicView/PayMusicVC.swift
@@ -11,12 +11,34 @@
import MediaPlayer
import RxRelay
class PayMusicVC: BaseVC {
@objc enum PlayMusicState:Int{
            case playing = 1
            case paurse = 2
            case end = 3
            case next = 4
}
@objc protocol PayMusicDelegate{
            func playState(_ state:PlayMusicState)
            @objc optional    func playListen(currentInterval:TimeInterval,totalInterval:TimeInterval)
}
class PayMusicVC: BaseVC{
            private var coverImage:UIImageView!
            private var label_name:UILabel!
            private var btn_handle:UIButton!
            private var btn_handleClose:UIButton!
            private var audioPlayer:AudioPlayer!
            private var isAniLoop:Bool = false
            private var meditationModel:MeditationModel?{
                        didSet{
                                    if let m = meditationModel{
                                                coverImage.sd_setImage(with: URL(string: m.coverUrl),placeholderImage: UIImage(named: "login_top_bg"))
                                                label_name.text = m.meditationTitle
                                    }
                        }
            }
            private init() {
                        super.init(nibName: nil, bundle: nil)
@@ -29,6 +51,10 @@
    override func viewDidLoad() {
        super.viewDidLoad()
                        audioPlayer = AudioPlayer.getSharedInstance()
                        let tap = UITapGestureRecognizer(target: self, action: #selector(showDetailAction))
                        coverImage.isUserInteractionEnabled = true
                        coverImage.addGestureRecognizer(tap)
    }
            override func setUI() {
@@ -58,38 +84,57 @@
                        btn_handle = UIButton(type: .custom)
                        btn_handle.setImage(UIImage(named: "icon_play_purse"), for: .normal)
                        btn_handle.setImage(UIImage(named: "icon_play_small"), for: .selected)
                        btn_handle.addTarget(self, action: #selector(tapHandleAction), for: .touchUpInside)
                        view.addSubview(btn_handle)
                        btn_handle.snp.makeConstraints { make in
                        btn_handleClose = UIButton(type: .custom)
                        btn_handleClose.setImage(UIImage(named: "icon_play_close"), for: .normal)
                        btn_handleClose.addTarget(self, action: #selector(closeAction), for: .touchUpInside)
                        view.addSubview(btn_handleClose)
                        let statckView = UIStackView(arrangedSubviews: [btn_handle,btn_handleClose])
                        statckView.axis = .horizontal
                        statckView.distribution = .equalSpacing
                        statckView.spacing = 18
                        view.addSubview(statckView)
                        statckView.snp.makeConstraints { make in
                                    make.right.equalToSuperview().offset(-24.5)
                                    make.centerY.equalToSuperview()
                                    make.width.height.equalTo(28)
                        }
            }
            static func show(){
                         let vc = PayMusicVC()
                         let tabBarHeight = JQ_currentViewController().navigationController?.tabBarController?.tabBar.height ?? 0
                        JQ_currentViewController().navigationController?.tabBarController?.addChild(vc)
                        JQ_currentViewController().navigationController?.tabBarController?.view.addSubview(vc.view)
                        vc.view.snp.makeConstraints { make in
                                    make.left.equalTo(18.5)
                                    make.right.equalTo(-18.5)
                                    make.height.equalTo(46.5)
                                    make.bottom.equalToSuperview().offset(-(tabBarHeight))
            static func show(model:MeditationModel){
                        if let tabBarVC = JQ_currentViewController().navigationController?.tabBarController as? BaseTabBarVC{
                                    if !tabBarVC.children.contains(where: {$0 is PayMusicVC}){
                                                let vc = PayMusicVC()
                                                vc.view.isHidden = true
                                                vc.meditationModel = model
                                                let tabBarHeight = JQ_currentViewController().navigationController?.tabBarController?.tabBar.height ?? 0
                                                JQ_currentViewController().navigationController?.tabBarController?.addChild(vc)
                                                JQ_currentViewController().navigationController?.tabBarController?.view.addSubview(vc.view)
                                                vc.view.snp.makeConstraints { make in
                                                            make.left.equalTo(18.5)
                                                            make.right.equalTo(-18.5)
                                                            make.height.equalTo(46.5)
                                                            make.bottom.equalToSuperview().offset(-(tabBarHeight))
                                                }
                                                vc.startRunloopAni()
                                    }
                        }
                        vc.startRunloopAni()
                        var testURL = [URL]()
                        testURL.append(URL(string: "https://downsc.chinaz.net/files/download/sound1/201206/1638.mp3")!)
                        testURL.append(URL(string: "https://downsc.chinaz.net/Files/DownLoad/sound1/201906/11582.mp3")!)
                        testURL.append(URL(string: "https://www.cambridgeenglish.org/images/153149-movers-sample-listening-test-vol2.mp3")!)
                        vc.audioPlayer.playAt(firstPlayIndex: 2, urls: testURL)
            }
            @objc func showDetailAction(){
                        if let id = meditationModel?.id{
                                    let vc = HomeItemDetailVC(id: id)
                                    vc.hidesBottomBarWhenPushed = true
                                    JQ_currentNavigationController().pushViewController(vc)
                        }
            }
            private func startRunloopAni(){
                        isAniLoop = true
                        // 创建旋转动画
                        let rotationAnimation = CABasicAnimation(keyPath: "transform.rotation.z")
                        rotationAnimation.fromValue = 0
@@ -97,25 +142,51 @@
                        rotationAnimation.duration = 5 // 动画持续时间
                        rotationAnimation.repeatCount = .greatestFiniteMagnitude // 无限重复
                        coverImage.layer.add(rotationAnimation, forKey: nil)
            }
            private func stopRunloopAni(){
                        isAniLoop = false
                        coverImage.layer.removeAllAnimations()
            }
            @objc func tapHandleAction(_ btn:UIButton){
                        btn.isSelected = !btn.isSelected
                        if btn.isSelected{
                                    self.audioPlayer.bgmPlayer?.pause()
                                    self.audioPlayer.masterPlayer?.pause()
                                    self.stopRunloopAni()
                        }else{
                                    self.audioPlayer.bgmPlayer?.play()
                                    self.audioPlayer.masterPlayer?.play()
                                    self.startRunloopAni()
                        }
            }
            @objc func closeAction(_ btn:UIButton){
                        CommonAlertView.show(title: "提示", content: "是否关闭当前播放音频?") { state in
                                    if state{
                                                self.audioPlayer.clean()
                                                self.view.removeFromSuperview()
                                                self.removeFromParent()
                                    }
                        }
            }
}
class AudioPlayer {
            private var player:AVPlayer?
            private var BGMplayer:AVPlayer?
            private var playIndex:Int = 0 //播放的角标
            private(set) var bgmPlayer:AVPlayer? // 背景音
            private(set) var scenePlayer:AVPlayer? //场景音
            private(set) var masterPlayer:AVPlayer? //大师音
            private(set) var playIndex:Int = 0 //播放的角标
            private var cacheDirectory:URL!
            private let session = URLSession.shared
            private var urls = [URL]()
            private var timer:Timer?
            private(set) var times = BehaviorRelay<Int?>(value: nil)
            private(set) var times = BehaviorRelay<Int?>(value: nil) //倒计时定时器
            private static var _sharedInstance: AudioPlayer?
            private(set) var meditationModel:MeditationModel?
            weak var delegate:PayMusicDelegate?
            class func getSharedInstance() -> AudioPlayer {
                        guard let instance = _sharedInstance else {
@@ -138,43 +209,81 @@
                        _sharedInstance = nil
            }
            func playAt(firstPlayIndex:Int,urls:[URL]){
                        self.playIndex = firstPlayIndex
            func clean(){
                        self.bgmPlayer?.pause()
                        self.masterPlayer?.pause()
                        self.meditationModel = nil
                        self.timer = nil
                        AudioPlayer.destroy()
            }
                        autoreleasepool{
            func playBGMAt(firstPlayIndex:Int,model:MeditationModel,delegate:PayMusicDelegate?){
                        self.delegate = delegate
                        self.playIndex = firstPlayIndex
                        self.meditationModel = model
                        let urls = model.backgroundUrl.components(separatedBy: ",").map { str in
                                    return URL(string: str)!
                        }
                        let masterUrl = URL(string: model.tutorAudioUrl)
                        autoreleasepool{[unowned self] () in
                                    for url in urls {
                                                checkCacheAudio(from: url) { _, url in
                                                self.checkCacheAudio(from: url) {[unowned self] _, url in
                                                            self.urls.append(url)
                                                }
                                    }
                                    player = AVPlayer(url: self.urls[firstPlayIndex])
                                    player?.play()
                                    self.bgmPlayer = AVPlayer(url: self.urls[firstPlayIndex])
                                    self.bgmPlayer?.play()
                                    if masterUrl != nil{
                                                self.masterPlayer = AVPlayer(url: masterUrl!)
                                                self.masterPlayer?.play()
                                    }
                        }
                        self.player!.addPeriodicTimeObserver(forInterval: CMTimeMake(value: 1, timescale: 1), queue: DispatchQueue.main) { [unowned self](time) in
                        self.bgmPlayer!.addPeriodicTimeObserver(forInterval: CMTimeMake(value: 1, timescale: 1), queue: DispatchQueue.main) { [weak self](time) in
                                    guard let weakSelf = self else { return }
                                    weakSelf.delegate?.playState(.playing)
                                    //当前正在播放的时间
                                    let loadTime = CMTimeGetSeconds(time)
                                    //视频总时间
                                    let totalTime = CMTimeGetSeconds((self.player?.currentItem?.duration)!)
                                    let totalTime = CMTimeGetSeconds((weakSelf.bgmPlayer?.currentItem?.duration)!)
                                    var dic = [String:Any]()
                                    dic[MPMediaItemPropertyTitle] = "测试"
                                    dic[MPMediaItemPropertyArtist] = "心泉·疗愈"
                                    dic[MPMediaItemPropertyDiscNumber] = 1
                                    dic[MPNowPlayingInfoPropertyElapsedPlaybackTime] = loadTime
                                    dic[MPNowPlayingInfoPropertyPlaybackRate] = 1
                                    // 获取时长。item.duration.seconds 不凑效
                                    let asset = self.player?.currentItem?.asset
                                    dic[MPMediaItemPropertyPlaybackDuration] = CMTimeGetSeconds(asset!.duration)
                                                            dic[MPMediaItemPropertyArtwork] = MPMediaItemArtwork(boundsSize: CGSize(width: 50, height: 50), requestHandler: { s in
                                                                        return UIImage(named: "home_top_bg")!
                                                            })
                                    MPNowPlayingInfoCenter.default().nowPlayingInfo = dic
                                    if loadTime >= totalTime{
                                                if weakSelf.playIndex <= urls.count - 1{
                                                            weakSelf.next()
                                                            weakSelf.delegate?.playState(.next)
                                                }else{
                                                            weakSelf.delegate?.playState(.end)
                                                }
                                    }
                                    weakSelf.delegate?.playListen?(currentInterval: loadTime, totalInterval: totalTime)
                                    if let m = weakSelf.meditationModel{
                                                var dic = [String:Any]()
                                                dic[MPMediaItemPropertyTitle] = m
                                                dic[MPMediaItemPropertyArtist] = "心泉·疗愈"
                                                dic[MPMediaItemPropertyDiscNumber] = 1
                                                dic[MPNowPlayingInfoPropertyElapsedPlaybackTime] = loadTime
                                                dic[MPNowPlayingInfoPropertyPlaybackRate] = 1
                                                // 获取时长。item.duration.seconds 不凑效
                                                let asset = weakSelf.bgmPlayer?.currentItem?.asset
                                                dic[MPMediaItemPropertyPlaybackDuration] = CMTimeGetSeconds(asset!.duration)
                                                dic[MPMediaItemPropertyArtwork] = MPMediaItemArtwork(boundsSize: CGSize(width: 50, height: 50), requestHandler: { s in
                                                            return UIImage(named: "home_top_bg")!
                                                })
                                                MPNowPlayingInfoCenter.default().nowPlayingInfo = dic
                                    }
                        }
                        //播放完成
                        NotificationCenter.default.addObserver(self, selector: #selector(playbackEnd), name:NSNotification.Name.AVPlayerItemDidPlayToEndTime, object: nil)
//                        NotificationCenter.default.addObserver(self, selector: #selector(playbackEnd), name:NSNotification.Name.AVPlayerItemDidPlayToEndTime, object: nil)
                        setLockScreen()
@@ -188,60 +297,69 @@
                        }
            }
            func playBGMAt(_ url:String){
            /// 播放场景音乐
            func playSceneAt(_ url:String){
                        guard let URL = URL(string: url) else { return }
                        BGMplayer?.pause()
                        scenePlayer?.pause()
                        if BGMplayer == nil{
                                    BGMplayer = AVPlayer(url: URL)
                        if scenePlayer == nil{
                                    scenePlayer = AVPlayer(url: URL)
                        }else{
                                    BGMplayer?.replaceCurrentItem(with: AVPlayerItem(url: URL))
                                    scenePlayer?.replaceCurrentItem(with: AVPlayerItem(url: URL))
                        }
                        DispatchQueue.main.asyncAfter(delay: 3.0) {
                                    self.BGMplayer?.play()
                                    self.BGMplayer?.volume = Float(UserDefaultSettingViewModel.getSetting()?.volume ?? 0.5)
                                    self.scenePlayer?.play()
                                    self.scenePlayer?.volume = Float(UserDefaultSettingViewModel.getSetting()?.volume ?? 0.5)
                        }
                        //播放完成
                        NotificationCenter.default.addObserver(self, selector: #selector(playBGMbackEnd), name:NSNotification.Name.AVPlayerItemDidPlayToEndTime, object: nil)
//                        NotificationCenter.default.addObserver(self, selector: #selector(playBGMbackEnd), name:NSNotification.Name.AVPlayerItemDidPlayToEndTime, object: nil)
            }
            func dellocBGM(){
                        BGMplayer?.pause()
                        BGMplayer = nil
            func dellocScene(){
                        scenePlayer?.pause()
                        scenePlayer = nil
            }
            func pauseBGM(){
                        BGMplayer?.pause()
            func pauseScene(){
                        scenePlayer?.pause()
            }
            func playBGM(){
                        BGMplayer?.play()
            func playScene(){
                        scenePlayer?.play()
            }
            func next(){
                        playIndex += 1
                        let index = min((urls.count - 1), playIndex)
                        player?.replaceCurrentItem(with: AVPlayerItem(url: urls[index]))
                        player?.play()
                        bgmPlayer?.replaceCurrentItem(with: AVPlayerItem(url: urls[index]))
                        bgmPlayer?.play()
            }
            @objc private func playbackEnd(){
            }
//            @objc private func playbackEnd(){
//                        if bgmPlayer?.actionAtItemEnd == AVPlayer.ActionAtItemEnd.pause && playIndex <= urls.count - 1{
//                                    next()
//                                    self.delegate?.playState(.next)
//                        }else{
//                                    print("背景音乐播放完毕")
//                                    self.delegate?.playState(.end)
//                        }
//
//                        if masterPlayer?.actionAtItemEnd == AVPlayer.ActionAtItemEnd.pause{
//                                    print("导师播放完毕")
//                        }
//            }
            @objc private func playBGMbackEnd(){
                        self.BGMplayer?.seek(to: CMTimeMake(value: 0, timescale: 1))
                        self.BGMplayer?.play()
                        self.scenePlayer?.seek(to: CMTimeMake(value: 0, timescale: 1))
                        self.scenePlayer?.play()
            }
            func previous(){
                        playIndex -= 1
                        let index = max(0, playIndex)
                        player?.replaceCurrentItem(with: AVPlayerItem(url: urls[index]))
                        player?.play()
                        bgmPlayer?.replaceCurrentItem(with: AVPlayerItem(url: urls[index]))
                        bgmPlayer?.play()
            }
            func setLockScreen(){
@@ -255,7 +373,7 @@
                                                return .commandFailed
                                    }
                                    self.player?.seek(to: CMTime(seconds: event.positionTime, preferredTimescale: 1),
                                    self.bgmPlayer?.seek(to: CMTime(seconds: event.positionTime, preferredTimescale: 1),
                                                                              toleranceBefore: CMTime(seconds: 0, preferredTimescale: 1),
                                                                              toleranceAfter: CMTime(seconds: 0, preferredTimescale: 1))
@@ -265,13 +383,13 @@
                        // 播放
                        center.playCommand.addTarget {[unowned self] event in
                                    self.player?.play()
                                    self.bgmPlayer?.play()
                                    return .success
                        }
                        // 暂停
                        center.pauseCommand.addTarget {[unowned self] event in
                                    self.player?.pause()
                                    self.bgmPlayer?.pause()
                                    return .success
                        }