⑱ 模仿iOS 的Music App — 天籟之聲:台灣原住民音樂 (下)「音樂播放器」
Published in
17 min readJul 30, 2024
終於來到最後一篇文章,即將要介紹如何製作「音樂播放器」:
- Import AVFoundation
- 功能:下一首、上一首、重複播放、返回播放、調整音量大小、時間軸
一、建立UI 畫面 + Third (New Swift File)
如果希望可以修改按鈕顏色,可以調整Foreground,Image 選擇符號
二、IBOutlet
解說
UIImageView
- imageView:這是一個
UIImageView
,用於顯示音樂封面圖片
UILabel
- musicName:這是一個
UILabel
,用於顯示當前播放的音樂名稱 - singerName:這是一個
UILabel
,用於顯示當前播放音樂的歌手名稱 - startTime:這是一個
UILabel
,用於顯示音樂播放的開始時 - endTime:這是一個
UILabel
,用於顯示音樂播放的結束時間
UISlider
- playTimeSlider:這是一個
UISlider
,用於顯示和調整音樂的播放進度 - volumeSlider:這是一個
UISlider
,用於調整音樂播放的音量大小
UIButton
- backButton:這是一個
UIButton
,用於實現音樂回到上一首的功能 - nextButton:這是一個
UIButton
,用於實現音樂跳到下一首的功能 - pauseButton:這是一個
UIButton
,用於實現音樂暫停和播放的功能 - repeatButton:這是一個
UIButton
,用於設置音樂重複播放的功能 - shuffleButton:這是一個
UIButton
,用於設置音樂隨機播放的功能
三、IBAction
repeatButton: 重複播放
shuffleButton: 隨機播放
stopButton 暫停播放
nextSound & backSoundButton, volumnChange 下一首、前一首、音量
timeChange 時間軸
四、更新畫面: 設定五種Function
- playMusic 播放音樂
- updateUI 更新歌曲、歌手、畫面圖片
- nowPlayTime 顯示播放幾秒func
- updateMusicUI 更新歌曲時確認歌的時間讓Slider也更新
- musicEnd 確認音樂結束
五、程式碼解析
// 切換重複播放狀態
@IBAction func repeatButton(_ sender: UIButton) {
// 每次點擊按鈕,重複播放索引增加1
repeatIndex += 1
// 如果重複播放索引等於1,表示開啟重複播放模式
if repeatIndex == 1 {
// 設置按鈕圖標為單曲重複
repeatButton.setImage(setbuttonImage(systemName: "repeat.1", pointSize: 15), for: .normal)
// 設置重複播放狀態為 true
repeatBool = true
} else {
// 如果重複播放索引不等於1,表示關閉重複播放模式
// 重置重複播放索引為0
repeatIndex = 0
// 設置按鈕圖標為循環播放
repeatButton.setImage(setbuttonImage(systemName: "repeat", pointSize: 15), for: .normal)
// 設置重複播放狀態為 false
repeatBool = false
}
}
// 切換隨機播放狀態
@IBAction func shuffleButton(_ sender: Any) {
// 每次點擊按鈕,隨機播放索引增加1
shuffleIndex += 1
// 如果隨機播放索引等於1,表示開啟隨機播放模式
if shuffleIndex == 1 {
// 設置按鈕圖標為已填充的隨機播放
shuffleButton.setImage(setbuttonImage(systemName: "shuffle.circle.fill", pointSize: 20), for: .normal)
print("musicIndex\(musicIndex)")
print("allmusic.count\(allmusic.count)")
} else {
// 如果隨機播放索引不等於1,表示關閉隨機播放模式
// 重置隨機播放索引為0
shuffleIndex = 0
// 設置按鈕圖標為隨機播放
shuffleButton.setImage(setbuttonImage(systemName: "shuffle", pointSize: 15), for: .normal)
}
}
// 暫停或播放音樂
@IBAction func stopButton(_ sender: UIButton) {
// 每次點擊按鈕,播放音樂索引增加1
playmusicIndex += 1
// 如果播放音樂索引等於1,表示暫停播放
if playmusicIndex == 1 {
// 暫停播放器
player.pause()
// 設置按鈕圖標為播放按鈕
pauseButton.setImage(setbuttonImage(systemName: "play.fill", pointSize: 30), for: .normal)
// 設置按鈕背景顏色
pauseButton.tintColor = UIColor(red: 47/255.0, green: 47/255.0, blue: 59/255.0, alpha: 1.0)
} else {
// 如果播放音樂索引不等於1,表示恢復播放
// 播放播放器
player.play()
// 重置播放音樂索引為0
playmusicIndex = 0
// 設置按鈕圖標為暫停按鈕
pauseButton.setImage(setbuttonImage(systemName: "pause.fill", pointSize: 30), for: .normal)
// 設置按鈕背景顏色
pauseButton.tintColor = UIColor(red: 47/255.0, green: 47/255.0, blue: 59/255.0, alpha: 1.0)
}
}
// 播放下一首音樂
@IBAction func nextSoundButton(_ sender: UIButton) {
// 調用播放下一首音樂的函數
playNextSound()
// 設置按鈕圖標為暫停按鈕
pauseButton.setImage(setbuttonImage(systemName: "pause.fill", pointSize: 30), for: .normal)
// 設置按鈕背景顏色
pauseButton.tintColor = UIColor(red: 47/255.0, green: 47/255.0, blue: 59/255.0, alpha: 1.0)
}
// 播放上一首音樂
@IBAction func backSoundButton(_ sender: UIButton) {
// 調用播放上一首音樂的函數
backSound()
// 設置按鈕圖標為暫停按鈕
pauseButton.setImage(setbuttonImage(systemName: "pause.fill", pointSize: 30), for: .normal)
// 設置按鈕背景顏色
pauseButton.tintColor = UIColor(red: 47/255.0, green: 47/255.0, blue: 59/255.0, alpha: 1.0)
}
// 調節音量
@IBAction func volumeChange(_ sender: UISlider) {
// 設置播放器的音量為音量滑桿的值
player.volume = volumeSlider.value
}
// 拖動播放進度條
@IBAction func timeChage(_ sender: UISlider) {
// 設置改變時間為滑桿的值
let changeTime = Int64(sender.value)
// 創建 CMTime 來控制音樂播放到的位置
let time: CMTime = CMTimeMake(value: changeTime, timescale: 1)
// 將播放器的播放時間設置為滑桿拖動到的位置
player.seek(to: time)
print("sender.value\(sender.value)")
print("sender.maximumValue\(sender.maximumValue)")
}
// 更新歌曲、歌手、畫面圖片
func updateUI() {
// 設置音樂名稱標籤的文字為當前播放的音樂名稱
musicName.text = allmusic[musicIndex].musicName
// 設置歌手名稱標籤的文字為當前播放的歌手名稱
singerName.text = allmusic[musicIndex].singer
// 設置圖片視圖的圖片為當前播放的專輯封面
imageView.image = UIImage(named: allmusic[musicIndex].musicPic)
}
// 更新歌曲時確認歌的時間讓 Slider 也更新
func updateMusicUI() {
// 獲取播放器項目的資產持續時間
guard let timeduration = playerItem?.asset.duration else {
return
}
// 將持續時間轉換為秒數
let seconds = CMTimeGetSeconds(timeduration)
// 設置結束時間標籤的文字為轉換後的秒數
endTime.text = timeShow(time: seconds)
// 設置播放進度滑桿的最小值為0
playTimeSlider.minimumValue = 0
// 設置播放進度滑桿的最大值為歌曲的總秒數
playTimeSlider.maximumValue = Float(seconds)
// 設置播放進度滑桿為可連續拖動
playTimeSlider.isContinuous = true
print("second:\(seconds)")
}
// 顯示播放時間
func timeShow(time: Double) -> String {
// 將時間轉換為分鐘和秒數
let time = Int(time).quotientAndRemainder(dividingBy: 60)
// 返回格式化的時間字符串
let timeString = ("\(String(time.quotient)) : \(String(format:"%02d", time.remainder))")
return timeString
}
// 播放當前時間
func nowPlayTime() {
// 添加時間觀察者,每秒調用一次
player.addPeriodicTimeObserver(forInterval: CMTimeMake(value: 1, timescale: 1), queue: DispatchQueue.main, using: { (CMTime) in
// 如果播放器準備就緒
if self.player.currentItem?.status == .readyToPlay {
// 獲取播放器的當前播放時間
let currenTime = CMTimeGetSeconds(self.player.currentTime())
// 設置播放進度滑桿的值為當前播放時間
self.playTimeSlider.value = Float(currenTime)
// 設置開始時間標籤的文字為當前播放時間
self.startTime.text = self.timeShow(time: currenTime)
}
})
}
// 確認音樂結束
func musicEnd() {
// 添加通知觀察者,監聽音樂播放結束事件
NotificationCenter.default.addObserver(forName: NSNotification.Name.AVPlayerItemDidPlayToEndTime, object: nil, queue: .main) { (_) in
// 如果重複播放狀態為 true
if self.repeatBool {
// 將播放器的播放時間設置為0,從頭開始播放
let musicEndTime: CMTime = CMTimeMake(value: 0, timescale: 1)
self.player.seek(to: musicEndTime)
self.player.play()
} else {
// 否則播放下一首音樂
self.playNextSound()
}
}
}
// 播放音樂
func playMusic() {
// 獲取音樂文件的路徑
guard let fileUrl = Bundle.main.url(forResource: allmusic[musicIndex].music, withExtension: "mp3") else {
print("音樂檔案未找到")
return
}
// 創建播放器項目,保存播放的音樂
playerItem = AVPlayerItem(url: fileUrl)
// 將播放器的當前項目替換為新的播放器項目
player.replaceCurrentItem(with: playerItem)
// 播放音樂
player.play()
}
// 設定按鈕圖示大小與圖案
func setbuttonImage(systemName: String, pointSize: Int) -> UIImage? {
// 創建圖標配置,設置大小和比例
let sfsymbol = UIImage.SymbolConfiguration(pointSize: CGFloat(pointSize), weight: .bold, scale: .large)
// 創建圖標圖片,使用系統圖標名稱和配置
let sfsymbolImage = UIImage(systemName: systemName, withConfiguration: sfsymbol)
// 返回圖標圖片
return sfsymbolImage
}
// 類屬性
var playedIndices: [Int] = []
var previousIndex: Int? = nil
// 隨機播放下一首歌
func playNextSound() {
// 如果隨機播放模式開啟
if shuffleIndex == 1 {
// 如果所有歌曲都已播放過,重置播放記錄
if playedIndices.count == allmusic.count {
playedIndices.removeAll()
}
var nextIndex: Int
repeat {
nextIndex = Int.random(in: 0..<allmusic.count)
} while nextIndex == musicIndex || nextIndex == previousIndex || playedIndices.contains(nextIndex)
playedIndices.append(nextIndex)
previousIndex = musicIndex
musicIndex = nextIndex
updateUI()
playMusic()
updateMusicUI()
} else {
musicIndex += 1
if musicIndex < allmusic.count {
updateUI()
playMusic()
updateMusicUI()
} else {
musicIndex = 0
updateUI()
playMusic()
updateMusicUI()
}
}
}
// 隨機播放上一首歌
func backSound() {
// 如果隨機播放模式開啟
if shuffleIndex == 1 {
// 如果所有歌曲都已播放過,重置播放記錄
if playedIndices.count == allmusic.count {
playedIndices.removeAll()
}
var nextIndex: Int
repeat {
nextIndex = Int.random(in: 0..<allmusic.count)
} while nextIndex == musicIndex || nextIndex == previousIndex || playedIndices.contains(nextIndex)
playedIndices.append(nextIndex)
previousIndex = musicIndex
musicIndex = nextIndex
updateUI()
playMusic()
updateMusicUI()
} else {
musicIndex -= 1
if musicIndex >= 0 {
updateUI()
playMusic()
updateMusicUI()
} else {
musicIndex = allmusic.count - 1
updateUI()
playMusic()
updateMusicUI()
}
}
}
}
完整程式碼(建議下載iPhone15 Pro)
上篇
中篇-1
中篇-2