Swift— 做個播放器吧(功能篇-2)
接續上次的教學,首先我們先增加一個必須的功能,那就是「播放清單」,這邊我們會使用 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 的方法就不多加贅述,因為就看每個人想怎麼設計,這邊我就用原生的樣式來製作:
建立完成 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 來進行歌曲更換了:
目前我們的播放器還缺少了,上一首、下一首以及重複播放功能,首先我們先來實作上下首的功能,其實他們兩者只有差別在如何判斷而已:
- 上一首:
@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 連結:
如果有什麼想增加或是額外的功能,可以提供出來再看看能不能補充上去。