// // PlayController.swift // cloudmusic // // Created by Qihua Pan on 2020/7/3. // Copyright © 2020 Qihua Pan. All rights reserved. // import UIKit import AVFoundation import MediaPlayer class PlayController: UIViewController { @IBOutlet weak var musicName: UILabel! @IBOutlet weak var progress: UIProgressView! @IBOutlet weak var timeLabel: UILabel! @IBOutlet weak var playButton: UIButton! @IBOutlet weak var prevButton: UIButton! @IBOutlet weak var nextButton: UIButton! var activeIndex:Int = -1 var playList:[Music] = [] private var avplayer: AVPlayer!//播放器对象 private var playerItem: AVPlayerItem!//播放资源对象 var timeObserver: Any! //时间观察者 @IBAction func pressPlay(_ sender: UIButton) { if self.avplayer.rate>0{ self.avplayer.pause() self.playButton.setImage(UIImage(named: "play"), for: .normal) }else{ self.avplayer.play() self.playButton.setImage(UIImage(named: "pause"), for: .normal) } } @IBAction func pressNext(_ sender: UIButton) { if self.activeIndex + 1 == self.playList.count{ self.activeIndex = 0 }else{ self.activeIndex += 1 } self.play() } @IBAction func pressPrev(_ sender: UIButton) { if self.activeIndex - 1 == -1{ self.activeIndex = self.playList.count-1 }else{ self.activeIndex -= 1 } self.play() } override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view. progress.transform = CGAffineTransform(scaleX: 1, y: 3.0) self.playList=self.getPlayList() if(self.playList.count>0){ self.activeIndex=0 self.play() } } override func viewDidAppear(_ animated: Bool) { self.playList=self.getPlayList() if self.playList.count==0{ self.musicName.text="" self.alertModal(message: "当前播放列表为空!") } if self.playList.count<2{ self.prevButton.isHidden=true self.nextButton.isHidden=true }else{ self.prevButton.isHidden=false self.nextButton.isHidden=false } } func addPlayerItemObserver(){ // 为AVPlayerItem添加status属性观察,得到资源准备好,开始播放视频 playerItem?.addObserver(self, forKeyPath: "status", options: .new, context: nil) // 监听AVPlayerItem的loadedTimeRanges属性来监听缓冲进度更新 playerItem?.addObserver(self, forKeyPath: "loadedTimeRanges", options: .new, context: nil) // NotificationCenter.default.addObserver(self, // selector: #selector(playerItemDidReachEnd(notification:)), // name: .AVPlayerItemDidPlayToEndTime, object: playerItem) } /// 通过KVO监控播放器状态 /// /// - parameter keyPath: 监控属性 /// - parameter object: 监视器 /// - parameter change: 状态改变 /// - parameter context: 上下文 override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) { guard let object = object as? AVPlayerItem else { return } guard let keyPath = keyPath else { return } if keyPath == "status" { if object.status == .readyToPlay { //当资源准备好播放,那么开始播放视频 self.avplayer.play() self.playButton.setImage(UIImage(named: "pause"), for: .normal) } else if object.status == .failed || object.status == .unknown { self.alertModal(message: "解析音频失败") } } else if keyPath == "loadedTimeRanges" { let loadedTime = availableDurationWithplayerItem() let f = CMTimeGetSeconds(object.duration)/loadedTime self.progress.progress=Float(f) if f==1.0{ self.progress.isHidden=true } print("当前加载进度\(CMTimeGetSeconds(object.duration)/loadedTime)") } } func addProgressObserver(){ // 这里设置每秒执行一次. timeObserver = self.avplayer.addPeriodicTimeObserver(forInterval: CMTimeMake(value: Int64(1.0), timescale: Int32(1.0)), queue: DispatchQueue.main) { [weak self] (time: CMTime) in self?.updateProgress(time) } } func updateProgress(_ time: CMTime) { // CMTimeGetSeconds函数是将CMTime转换为秒,如果CMTime无效,将返回NaN guard let playerItem = playerItem else { return } let currentTime = CMTimeGetSeconds(time) let totalTime = CMTimeGetSeconds(playerItem.duration) // 更新显示的时间和进度条 self.timeLabel.text = "\(self.formatPlayTime(seconds: CMTimeGetSeconds(time)))/\(self.formatPlayTime(seconds: totalTime))" self.progress.setProgress(Float(currentTime/totalTime), animated: true) print("当前已经播放\(self.formatPlayTime(seconds: CMTimeGetSeconds(time)))") } func availableDurationWithplayerItem() -> TimeInterval { guard let loadedTimeRanges = self.avplayer.currentItem?.loadedTimeRanges, let first = loadedTimeRanges.first else { fatalError() } // 本次缓冲时间范围 let timeRange = first.timeRangeValue let startSeconds = CMTimeGetSeconds(timeRange.start) // 本次缓冲起始时间 let durationSecound = CMTimeGetSeconds(timeRange.duration)// 缓冲时间 let result = startSeconds + durationSecound// 缓冲总长度 return result } // 将秒转成时间字符串的方法,因为我们将得到秒。 func formatPlayTime(seconds: Double) -> String { if !seconds.isNaN && !seconds.isInfinite{ let min = Int(seconds / 60) let sec = Int(seconds.truncatingRemainder(dividingBy: 60)) return String(format: "%02d:%02d", min, sec) }else{ return "" } } func saveLocal(url: String,music: Music){ MusicRequest.saveMusic(url: url,music: music, callback: {(progress,url) in debugPrint("Download Progress: \(progress.fractionCompleted)") if progress.isFinished,let name=music.name { debugPrint("歌曲\(name)下载完毕, 文件绝对路径=\(url.absoluteString)") // if FileManager.default.fileExists(atPath: url.absoluteString){ // let filepath=url.absoluteString // music.filepath = filepath // self.saveContext() // } let filepath=url.absoluteString music.filepath = filepath self.saveContext() } }) } func play(){ self.play(music: self.playList[self.activeIndex]) } func play1(){ // if let url=URL(string:url){ // let asset = AVURLAsset(url: url) // self.playerItem = AVPlayerItem(asset: asset) // self.avplayer = AVPlayer(playerItem: self.playerItem) // // 监听它状态的改变,实现kvo的方法 // self.addPlayerItemObserver() // self.addProgressObserver() // }else if let name=self.playList[self.activeIndex].name{ // self.alertModal(message:"无法解析🎵\(name)的播放地址") // } } func play(url:String,name:String){ if let url=URL(string:url){ let asset = AVURLAsset(url: url) self.playerItem = AVPlayerItem(asset: asset) self.avplayer = AVPlayer(playerItem: self.playerItem) // 监听它状态的改变,实现kvo的方法 self.addPlayerItemObserver() self.addProgressObserver() }else { self.alertModal(message:"无法解析🎵\(name)的播放地址") } } func play(index:Int){ self.activeIndex=index self.play() } func play(music:Music){ self.progress.isHidden=false if let name=music.name{ self.musicName.text=name if let filepath=music.filepath{ debugPrint("\(name)本地播放地址\(filepath)") self.play(url: filepath,name:name) }else if let url=music.url{ debugPrint("\(name)流式播放地址\(url)") self.play(url: url,name:name) self.saveLocal(url: url, music: music) }else{ debugPrint("查找\(name)流式播放地址") MusicRequest.searchUrl(id: music.id, callback: {(json) in debugPrint("json:\(String(describing: json))") if let res=json{ if res["code"]==200{ if res["data"].array != nil && res["data"].array!.count>0 && res["data"][0]["url"].stringValue.count>0{ let url = res["data"][0]["url"].stringValue debugPrint("音乐🎵播放地址:\(url)") music.url=url self.saveContext() self.saveLocal(url: url, music: music) self.play(url: url,name:name) }else{ self.alertModal(message:"无法解析🎵\(String(describing: name))的播放地址") } }else{ self.alertModal(message:"无法查找🎵\(String(describing: name))的播放地址") } } }) } } } /* // MARK: - Navigation // In a storyboard-based application, you will often want to do a little preparation before navigation override func prepare(for segue: UIStoryboardSegue, sender: Any?) { // Get the new view controller using segue.destination. // Pass the selected object to the new view controller. } */ }