You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
296 lines
11 KiB
296 lines
11 KiB
//
|
|
// 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.
|
|
}
|
|
*/
|
|
|
|
}
|
|
|