開發一款 iOS 音樂播放器的五個點

播放很簡單

通常分爲兩個過程,準備播放,與播放

準備播放,包括準備播放資源、播放器初始化和播放器準備好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

第一點,進度條怎麼作?

111

通常進度條,會作兩件事,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

第二點,亂序播放與循環播放,怎麼作?

222

使用亂序播放與循環播放兩個按鈕,針對的都是下一曲,以及以後的曲子

他們不會影響當前的歌曲播放

因此這兩個按鈕點擊,都是改 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 保活就能夠了

777

do {
            //keep alive audio at background
            try AVAudioSession.sharedInstance().setCategory(AVAudioSession.Category.playback)
        } catch _ { }
        
        do {
            try AVAudioSession.sharedInstance().setActive(true)
        } catch _ { }
複製代碼

第四點,播放器的遠程控件事件

屏幕的底部彈框中,播放器的控件事件

333

鎖屏後,播放器的控件事件

333

首先要接收遠程的控件事件

//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")
            }
        }
    }
複製代碼

第五點,怎麼播放多種文件,mp三、m4a?

經過二進制的 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
複製代碼

本文代碼: github.com/coyingcat/m…

本文基於 bpolat/Music-Player

相關文章
相關標籤/搜索