#10 — 模仿 iOS 的 Music App 製作情歌點唱機

呈現畫面:

銜接#6 —利用 page control,segmented control,button & gesture 更換內容作品做了改動並新增功能,有興趣可以點下方連結。

使用元件包含:

UIImageView

Page control

Label

Segmented control

Button

Gesture

Slider

和上次一樣並沒有多使用其他額外元件,所以接下來我只提一些比較重要的部分。

新增了循環播放、隨機播放的Button,先宣告兩個變數randomIndex、repeatIndex,在判別值為0或1區分Button是否被點選,被點選時也會更改Button的Image。

var randomIndex = 0
var repeatIndex = 0
@IBAction func repeatButton(_ sender: UIButton) {
repeatIndex += 1
if repeatIndex == 1{
repeatOutlet.setImage(UIImage(systemName: "repeat.1"), for: .normal)
}else{
repeatIndex = 0
repeatOutlet.setImage(UIImage(systemName: "repeat"), for: .normal)
}
}
@IBAction func randomButton(_ sender: UIButton) {
randomIndex += 1
if randomIndex == 1{
randomOutlet.setImage(UIImage(systemName: "shuffle.circle.fill"), for: .normal)
}else{
randomIndex = 0
randomOutlet.setImage(UIImage(systemName: "shuffle.circle"), for: .normal)
}
}

var randomIndex = 1時,點選上、下一首時要隨機撥放,以下一首Button舉例。

@IBAction func nextButton(_ sender: UIButton) {
if randomIndex == 1{
num = Int.random(in: 0...imageArray.count - 1)
sync()
musicPlayer()
updateMusciSlider()
}else{
num += 1
if num > 4{
num = 0
}
sync()
musicPlayer()
updateMusciSlider()
}
}

接著新增一個UISlider讓他拉動改變歌曲播放,左右Label顯示現在播放時間、歌曲總時間就相當困難了,我現在也還不是很清楚其中原理,所以先靠學長姐的寫法才能寫出來。

先寫一個Function,裡面先利用playerItem!.asset.duration得到播放歌曲的總時間長度並轉換為Float64存在seconds,並用seconds更改Slider.maximumValue的最大值以及歌曲總時間的Label。

func updateMusciSlider(){
let duration = playerItem!.asset.duration
let seconds = CMTimeGetSeconds(duration)
totalTime = seconds
totalTimeLabel.text = timeShow(time: seconds)

playTimeSlider.minimumValue = 0
//最大值就是這首歌的總秒數
playTimeSlider.maximumValue = Float(seconds)
//slider會不會持續動作
playTimeSlider.isContinuous = true
}
func timeShow(time: Double) -> String {
//轉換成秒數
let time = Int(time).quotientAndRemainder(dividingBy: 60)
//顯示分鐘與秒數
let timeString = "\(String(time.quotient)) : \(String(format:"%02d", time.remainder))"
//回傳到顯示
return timeString
}

被更改最大值的UISlider就可以利用sender.value實現拉動Slider改變歌曲播放時間。

@IBAction func playTimeAction(_ sender: UISlider) {
let changeTime = Int64(sender.value)
let time = CMTime(value: changeTime, timescale: 1
)
player.seek(to: time)
}

再來需要設定UISlider要隨歌曲播放自動移動更加深奧,我也不太懂需要再努力XD。

func nowPlayTime(){
//播放的計數器從1開始每一秒都在播放
// let timeScale = CMTimeScale(NSEC_PER_SEC)
// let time = CMTime(seconds: 0.5, preferredTimescale: timeScale)
player.addPeriodicTimeObserver(forInterval: CMTimeMake(value: 1, timescale: 1), queue: DispatchQueue.main, using: { (CMTime) in
//如果音樂要播放
if self.player.currentItem?.status == .readyToPlay{
//就會得到player播放的時間
let currenTime = CMTimeGetSeconds(self.player.currentTime())
//Slider移動就會等於currenTime的時間
self.playTimeSlider.value = Float(currenTime)
//顯示播放了幾秒
self.songTimeLabel.text = self.timeShow(time: currenTime)
self.songProgressBar(time:currenTime)
}
})
}

最後是自動播放的部分,由於歌曲結束會收到 Notification它會發送通知AVPlayerItemDidPlayToEndTime,在裡面先判斷重複播放歌曲是否有打開,沒打開再判斷是否有開啟隨機播放。

func musicEnd(){
NotificationCenter.default.addObserver(forName: NSNotification.Name.AVPlayerItemDidPlayToEndTime, object: nil, queue: .main) { [self] (_) in
if repeatIndex == 1 {
let musicEndTime: CMTime = CMTimeMake(value: 0, timescale: 1)
player.seek(to: musicEndTime)
player.play()
}else{
if randomIndex == 1{
num = Int.random(in: 0...imageArray.count - 1)
sync()
musicPlayer()
updateMusciSlider()
}else{
num += 1
if num > 4{
num = 0
}
sync()
musicPlayer()
updateMusciSlider()
}
}
}
}

剩下的鎖定背景播放還沒搞定有瑕疵就先不附上,之後有時間再把它完成,另外如果對唱片動畫、隨歌曲時間移動的CAShapeLayer()有興趣都可以參考我的GitHub~~

參考資料:

(以下是某位大大寫的程式,可以讓暫停、播放動畫不中斷超神的~)

GitHub網址:

--

--