| | |
| | | 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) |
| | |
| | | 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() { |
| | |
| | | |
| | | 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 |
| | |
| | | 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 { |
| | |
| | | _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() |
| | | |
| | |
| | | } |
| | | } |
| | | |
| | | 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(){ |
| | |
| | | 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)) |
| | | |
| | |
| | | |
| | | // 播放 |
| | | 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 |
| | | } |
| | | |