Swift— 做個播放器吧(功能篇-2)

Jeremy Xue
9 min readJun 12, 2018

--

接續上次的教學,首先我們先增加一個必須的功能,那就是「播放清單」,這邊我們會使用 UITableView 來製作我們的播放清單,但是因為新增、刪除和修改都用不上,因為我們不需要刪除,檔案都放在 APP 中,所以我們就單純建立一個固定資料的播放清單。

首先我們會建立一個 Model

struct SongModel {  let songImage:String
let songFile:String
init(songImage:String,songFile:String) {
self.songImage = songImage
self.songFile = songFile
}
}

之後我們就在我們 playlist 的 Array 新增我們的 Model:

var playlist = [   SongModel(songImage: "Chouchou musubi", songFile: "Aimer - Choucho Musubi"),
SongModel(songImage: "Chandelier", songFile: "Sia - Chandelier"),
SongModel(songImage: "Dusk till dawn", songFile: "ZAYN - Dusk Till Dawn")
]

這邊建立 TableView 的方法就不多加贅述,因為就看每個人想怎麼設計,這邊我就用原生的樣式來製作:

使用 TableView 所建立的 Playlist

建立完成 Playlist 後,現在我們需要的就是把我們選到的歌曲傳到我們播放器的頁面播放,並且更新我們的資訊,所以我這邊使用 delegate 來進行傳值,在此我們建立一個 protocol:

protocol ChangeSong {   func changeSong(songFile:String,songAlbum:String,songNumber:Int)}

這邊我希望他傳過來的值有檔案名稱、檔案相簿以及是歌曲中的第幾項,所以使用這幾個參數讓它傳遞到我們第一個畫面,之後我們在自己本身宣告一個這個協議的 delegate :

var changeSongDelegate:ChangeSong?

接下來我們就要來進行傳值的動作,這邊我們需要知道他所點選的 cell 為哪個,所以就要使用 tableView 中的 didSelectRowAt 方法:

func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {   if let playerVC = self.navigationController?.viewControllers[0] as? PlayerViewController{   self.changeSongDelegate = playerVC   changeSongDelegate?.changeSong(songFile:     playlist[indexPath.row].songFile, songAlbum: playlist[indexPath.row].songImage, songNumber: indexPath.row)   navigationController?.popViewController(animated: true)   }}

之後我們再回播放器的 ViewController 來執行我們 protocol 的方法,所以我們需要讓我們播放器的 ViewController 遵循 Change Song 的 protocol,之後就能來定義我們的方法了:

func changeSong(songFile: String, songAlbum: String,songNumber:Int) {// 將我們的歌曲檔案及圖片更新,並紀錄我們目前歌曲是第幾首
self.songAlbum = songAlbum
self.songFile = songFile
self.songNumber = songNumber
// 刪除所有 Observer
NotificationCenter.default.removeObserver(self)
// 重新設定所有設定
findSongPath()
updatePlayerUI()
// 這邊我們把建立 Observer 方式獨立出來,在新歌曲進來的時候再次建立
observeCurrentTime()
audioPlayer?.play()
playButton.setImage(UIImage(named: "icons8-pause"), for: UIControlState.normal)
}

如此一來,應該我們可以透過 Playlist 的 TableView 來進行歌曲更換了:

透過 Playlist 更換歌曲

目前我們的播放器還缺少了,上一首、下一首以及重複播放功能,首先我們先來實作上下首的功能,其實他們兩者只有差別在如何判斷而已:

  • 上一首:
@IBAction func previous(_ sender: UIButton) {    if let playlistVC = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "PlaylistViewController") as? PlaylistViewController {    songNumber -= 1    // 由此處判斷 songNumber 是否 < 0 ,如果是擇從最後一首歌開始    if songNumber < 0 {      songNumber  = playlistVC.playlist.count - 1    }    songFile = playlistVC.playlist[songNumber].songFile
songAlbum = playlistVC.playlist[songNumber].songImage
changeSong(songFile: songFile, songAlbum: songAlbum,songNumber: songNumber) }}
  • 下一首:
@IBAction func next(_ sender: UIButton) {  if let playlistVC = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "PlaylistViewController") as? PlaylistViewController {  // 此處是判斷假如 songNumber 超過我們的數量,則從第一首歌開始
if songNumber >= playlistVC.playlist.count - 1 {
songNumber = 0 } else { songNumber += 1 } songFile = playlistVC.playlist[songNumber].songFile
songAlbum = playlistVC.playlist[songNumber].songImage
changeSong(songFile: songFile, songAlbum: songAlbum,songNumber: songNumber) }}

之後我們需要做到我們重複播放的功能,因此我們需要偵測歌曲是否播放完畢,所以我們在我們建立 observer 的方法中加上一個偵測播放完畢的 observer:

NotificationCenter.default.addObserver(self, selector: #selector(playToEndTime), name: NSNotification.Name.AVPlayerItemDidPlayToEndTime, object: playerItem)

當歌曲播放完畢時,就會去呼叫我們 playToEndTime 這個方法,接下來我們就可以來定義我們播放結束時該做些什麼的方法,這邊我們會去定義一個屬性叫做 recylcePlay 類型為 Bool,當這個值被設值為 true 則重複播放,設置為 false 則接下去播放下一首歌曲,根據這個規則來定義我們的方法:

@objc func playToEndTime() {// 如果重複播放為 true,則重複播放  if recyclePlay {
// 將時間移置最開始,接著播放
let targetTime:CMTime = CMTimeMake(0, 1)
audioPlayer?.seek(to: targetTime)
audioPlayer?.play()
} else { // 若重複播放為 false,那麼就去尋找下一首歌
if let playlistVC = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "PlaylistViewController") as? PlaylistViewController {

// 這邊實作的方法跟我們下一首的方式大同小異
if songNumber >= playlistVC.playlist.count - 1 {

songNumber = 0
} else { songNumber += 1} songFile = playlistVC.playlist[songNumber].songFile
songAlbum = playlistVC.playlist[songNumber].songImage
changeSong(songFile: songFile, songAlbum: songAlbum,songNumber: songNumber) } }}

最後附上 GitHub 連結:

如果有什麼想增加或是額外的功能,可以提供出來再看看能不能補充上去。

--

--

Jeremy Xue
Jeremy Xue

Written by Jeremy Xue

Hi, I’m Jeremy. [好想工作室 — iOS Developer]

No responses yet