准备播放,包括准备播放资源、播放器初始化和播放器准备好git
其中准备播放资源github
var currentAudioPath:URL! currentAudio = readSongNameFromPlist(currentAudioIndex) if let path = Bundle.main.path(forResource: currentAudio, ofType: "mp3"){ currentAudioPath = URL(fileURLWithPath: path) } else{ alertSongExsit() }
播放器初始化和播放器准备好数组
var audioPlayer:AVAudioPlayer! audioPlayer = try? AVAudioPlayer(contentsOf: currentAudioPath) audioPlayer.delegate = self audioLength = audioPlayer.duration playerProgressSlider.maximumValue = CFloat(audioPlayer.duration) playerProgressSlider.minimumValue = 0.0 playerProgressSlider.value = 0.0 audioPlayer.prepareToPlay()
audioPlayer.play()
, 一行代码session
通常进度条,会作两件事,app
随着播放的推移,进度条的滑块会一直向前走,有一个音乐播放与进度条的进展的匹配less
进度条的滑块能够拖拽,来控制当前播放的地方,譬如能够回播,能够跳过dom
每次播放前,先设置进度条的进度,async
maximumValue 最大值,就是放完了,一首歌的时长ide
minimumValue 最小值,就是没播放,为 0优化
value 开始的时候,就是没播放,为 0
playerProgressSlider.maximumValue = CFloat(audioPlayer.duration) playerProgressSlider.minimumValue = 0.0 playerProgressSlider.value = 0.0
要想进度条的滑块会一直向前走,就要有一个计时器
func startTimer(){ if timer == nil { timer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(PlayerViewController.update(_:)), userInfo: nil,repeats: true) timer.fire() } } // 每隔一秒,去获取播放器的当前播放时间,刷新进度条 playerProgressSlider 的状态 @objc func update(_ timer: Timer){ if !audioPlayer.isPlaying{ return } let time = calculateTimeFromNSTimeInterval(audioPlayer.currentTime) playerProgressSlider.value = CFloat(audioPlayer.currentTime) }
由于以前滚动条的范围与播放器的时长,已经匹配好了
因此设置播放器的当前时间 currentTime ,就能够了
先暂停,再设置播放器的 currentTime,短暂的间隔后
过渡比较平滑,体验稍微好一些
@IBAction func changeAudioLocationSlider(_ sender : UISlider) { audioPlayer.pause() audioPlayer.currentTime = TimeInterval(sender.value) DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { self.audioPlayer.play() } }
audioPlayer.currentTime
使用乱序播放与循环播放两个按钮,针对的都是下一曲,以及以后的曲子
他们不会影响当前的歌曲播放
因此这两个按钮点击,都是改 UI , 改状态,当前歌曲播放完成后,起做用
或者当前没播放,下一次播放的时候,第一首歌曲播放完了,起做用
@IBAction func shuffleButtonTapped(_ sender: UIButton) { shuffleArray.removeAll() if sender.isSelected{ sender.isSelected = false shuffleState = false } else { sender.isSelected = true shuffleState = true } } @IBAction func repeatButtonTapped(_ sender: UIButton) { if sender.isSelected == true { sender.isSelected = false repeatState = false } else { sender.isSelected = true repeatState = true } }
乱序与循环,在 AVAudioPlayerDelegate 的播放完成回调方法中起做用 ,func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool){
就是指没点击乱序,只点击了循环,
if shuffleState == false && repeatState == true { //repeat same song, 重复播放就能够了 prepareAudio() playAudio()
就是没点击循环,只点击了乱序,
乱序,就要取随机数,
不循环,就要去除重复,这里就是把歌单播放一遍,就完了
// 经过建造一个数组来记录 var shuffleArray = [Int]() if shuffleState == true && repeatState == false { // 放了一首,添加一个 shuffleArray.append(currentAudioIndex) // 终止条件,放过的,很多于歌单的 if shuffleArray.count >= audioList.count { playButton.setImage( UIImage(named: "play"), for: UIControl.State()) return } // 一个可优化的循环 // 一直取随机数,若是取到没播放的,就添加下,跳出去,走下一步 // 不然一直在这里算 var randomIndex = 0 var newIndex = false while newIndex == false { randomIndex = Int(arc4random_uniform(UInt32(audioList.count))) if shuffleArray.contains(randomIndex) { newIndex = false }else{ newIndex = true } } // 算出结果,赋值过去 currentAudioIndex = randomIndex // 准备与播放 prepareAudio() playAudio()
就是点击了循环和乱序
乱序,就要取随机数,
乱序循环,这里就是把歌单乱序播放一遍,再重来
// 经过建造一个数组来记录 var shuffleArray = [Int]() if shuffleState == true && repeatState == true { //shuffle song endlessly // 放了一首,添加一个 shuffleArray.append(currentAudioIndex) // 重复条件,都播放过了,很多于歌单的,就清空重来 if shuffleArray.count >= audioList.count { shuffleArray.removeAll() } // 一个可优化的循环 // 一直取随机数,若是取到没播放的,就添加下,跳出去,走下一步 // 不然一直在这里算 var randomIndex = 0 var newIndex = false while newIndex == false { randomIndex = Int(arc4random_uniform(UInt32(audioList.count))) if shuffleArray.contains(randomIndex) { newIndex = false }else{ newIndex = true } } // 算出结果,赋值过去 currentAudioIndex = randomIndex // 准备与播放 prepareAudio() playAudio() }
实际上就是后台播放
设置一下后台模式,让 session 保活就能够了
do { //keep alive audio at background try AVAudioSession.sharedInstance().setCategory(AVAudioSession.Category.playback) } catch _ { } do { try AVAudioSession.sharedInstance().setActive(true) } catch _ { }
//LockScreen Media control registry if UIApplication.shared.responds(to: #selector(UIApplication.beginReceivingRemoteControlEvents)){ UIApplication.shared.beginReceivingRemoteControlEvents() UIApplication.shared.beginBackgroundTask(expirationHandler: { () -> Void in }) }
播放的时候,把播放信息,同步到锁屏与底部弹窗,
// This shows media info on lock screen - used currently and perform controls func showMediaInfo(){ let artistName = readArtistNameFromPlist(currentAudioIndex) let songName = readSongNameFromPlist(currentAudioIndex) MPNowPlayingInfoCenter.default().nowPlayingInfo = [MPMediaItemPropertyArtist : artistName, MPMediaItemPropertyTitle : songName] }
func remoteControlReceived
方法锁屏的时候,能够对播放器暂停与播放,点击上一首,与下一首
拉起底部弹窗的时候,也是
override func remoteControlReceived(with event: UIEvent?) { if event!.type == UIEvent.EventType.remoteControl{ switch event!.subtype{ case UIEventSubtype.remoteControlPlay: play(self) case UIEventSubtype.remoteControlPause: play(self) case UIEventSubtype.remoteControlNextTrack: next(self) case UIEventSubtype.remoteControlPreviousTrack: previous(self) default: print("There is an issue with the control") } } }
经过二进制的 data , 实例化 AVAudioPlayer 的方式
var player: AVAudioPlayer! var tempPath: String? if let mpPath = Bundle.main.path(forResource: str, ofType: "mp3"){ tempPath = mpPath } if let maPath = Bundle.main.path(forResource: str, ofType: "m4a"){ tempPath = maPath } guard let path = tempPath, let playerTmp = try? AVAudioPlayer(data: Data(contentsOf: URL(fileURLWithPath: path))) else{ return } self.player = playerTmp
本文基于 bpolat/Music-Player