準備播放,包括準備播放資源、播放器初始化和播放器準備好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()
, 一行代碼bash
通常進度條,會作兩件事,session
隨着播放的推移,進度條的滑塊會一直向前走,有一個音樂播放與進度條的進展的匹配app
進度條的滑塊能夠拖拽,來控制當前播放的地方,譬如能夠回播,能夠跳過less
每次播放前,先設置進度條的進度,dom
maximumValue 最大值,就是放完了,一首歌的時長async
minimumValue 最小值,就是沒播放,爲 0ide
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