模仿 iOS 的 Music App 製作周興哲Eric專屬播放電台(包含進階功能)

Lucas Lin
彼得潘的 Swift iOS App 開發教室
9 min readMar 22, 2020

--

苦不堪言

這次花了我整整兩天的時間在製作圖片、研究、找資源,透過Xcode要做播放很簡單,但是要做完該有的功能相對的對新手很需要時間和構想…。

這篇其實很難分享,因為有很多東西要說說不太完,當然也附上source code提供大家參考。

這次所提到的進階功能是包含了背景播放、播完後的動作、以及如何投射到AppleTV,同時實現播放暫停、下一首、上一首、自動更新Slider進度,以及重複播放和隨機播放。

當然歌曲庫的部分是從Youtube載下來,然後整體的樣式背景是透過sketch去找的,所以建議大家要做的時候,可以從這些地方找到資源。

畫面配置

可以去sketch中下載,附上網址提供給大家參考,關於使用教學,可能就是把圖片素材要用到的輸出Export到Finder,然後在拉到Xcode當素材用。

注意事項

由於有些功能在模擬器上不會有作用,所以我都是透過實際機器去跑和測試順便聽歌XD。

參考教學

過程中我參考了很多網站…不及備載。

匯入影片到Xcode

新增Album 的struct class

為了是資料庫的部分可以快速新增,不用太辛苦的一個一個建立,主要要記錄專輯名稱、專輯圖片、歌曲名稱

在ViewController Class與元件建立連結

將main.storyboard內的元件與ViewController建立連結(IBOutlet、IBAction),同時將一些會用到的東西都宣告好,如歌曲資料庫存放的陣列和一些圖片素材。

在viewDidLoad撰寫初始值

這邊會看到音量的部分並未存在於main.storyboard中,為了就是之後播放時候可以直接用系統音量來控制,所以這邊是動態新增音量slider到畫面。

同時在音樂資料庫部分建立album物件將每首歌曲都放入,之後播放時就會參考這個陣列的音樂,而且加上shuffle可以將陣列亂數排序,每次開起來就播放的順序就不同。

設定背景播放部分我先跳過,等等會講到setupRemoteTransportControls這個method在做什麼。

直接跳到playSong()

這個Method主要說明就是播放歌曲,也是我覺得很簡單也可以很複雜的地方,因為程式碼有點長,所以這邊用Github的方式呈現。

這個Method首先就是先判斷playIndex這個值是否有大於音樂資料庫的陣列長度,若超出則歸0,然後再呼叫自己Method一次繼續播放。

那如果一直按上一首的按鍵,就會發生-1的狀況,這時就需要利用陣列長度-1獲得最後一首歌的位置。

接著判斷完畢後,我們要將歌曲從資料庫內取出album物件,然後抓到歌曲名稱、專輯名稱、圖片,接著把這些資訊先set到指定的label上。

// 載入歌曲檔案,取得在手機APP中實際位置

let fileUrl = Bundle.main.url(forResource: songName, withExtension: “mp4”)!

//建立播放的項目

let playerItem = AVPlayerItem(url: fileUrl)

//先移除player播放器內現有的Item,這個很重要,不然壞掉的時候他就會一直亂播同首歌

player.removeAllItems()

//放入現在要播放的Item

player.replaceCurrentItem(with: playerItem)

//指定音量,這部分不太有作用,因為會按照使用者系統指定的音量來播放

player.volume = 0.5

播放進度Slider

由於整個播放時,我們有提供slider來顯示播放進度,所以重新播放一首歌時,都要重新歸0,並計算要播放的這首歌實際長度有多少!

// 重置slider和播放軌道

playbackSlider.setValue(Float(0), animated: true)

let targetTime:CMTime = CMTimeMake(value: Int64(0), timescale: 1)

player.seek(to: targetTime)

播放

那就是直接player.play() 就會開始播放,相反的player.pause()就會暫停。

增加監聽功能更新播放進度slider

這部分我們需要提供給監聽器完整的時間大小,好讓知道如何控制Slider。

// 更新slider時間value

let duration : CMTime = playerItem.asset.duration

let seconds : Float64 = CMTimeGetSeconds(duration)

playbackSlider.minimumValue = 0

playbackSlider.maximumValue = Float(seconds)

// 事件監聽:進度條

addProgressObserver(playerItem:playerItem)

變更UIButton圖案

// 設定播放按鈕圖案

controlButton.setImage(pauseIcon, for: UIControl.State.normal)

讓背景、鎖定時也能夠顯示歌曲資訊

這部分等等會講到這個Method。

// 設定背景當前播放資訊

setupNowPlaying()

播放、暫停鍵IBAction

透過圖片來判斷或者player.rate來判斷歌曲是否正在播放中,如果rate==0代表暫停中,若rate==1則代表歌曲播放中。

事件監聽、更新進度條

這裡大概是針對player的部分增加監聽,監聽頻率為每秒,所以這邊會不斷的計算時間進度,建議這邊在做的人,可以print出來,這樣就比較好追蹤時間的變化,然後再將結果setValue到slider,所以播放時slider就會跑。

播放上下首歌

這邊就比較簡單了,因為複雜的功能都寫在playSong(),所以這邊只要負責變更playIndex然後再呼叫playSong()

設定背景&鎖定播放

這個功能建議可以參閱Apple開發者說明,因為我也是看了很久,不是很懂,不過大概能改得動,其實就是針對背景和鎖定的播放器增加監聽,如果外面做了動做,也要讓裡面的player做動作。

設定背景播放時的歌曲資訊

這部分常常會不太懂是指什麼,所以先附上圖片給大家做參考。

透過這兩張圖,可以看到我們播放的歌曲正顯示在鎖定的畫面上,這時就是需要依賴以下的程式碼幫我們實現。

拖曳播放進度條時,要變更player播放軌道

這邊就是計算拖曳的幅度,然後計算實際要切換的秒數,然後透過player.seek的方式來變更,同時若使用者是暫停狀態下拖曳,拖曳完畢就會繼續播放。

重複播放、隨機播放功能

這邊介紹是如何在歌曲播放完畢後做處理,我們增加AVplayItemDidPlayToEndTime監聽事件,當播放完畢會進入這個區塊執行相對應的程式,所以我在這邊就是播放完畢就移除了進度條的監聽事件,不然之後繼續播放,會再增加一次事件,就會越來越慢。

判斷segment為重複播放還是隨機播放,這邊判斷若是隨機播放,我就給它在隨機處理一次音樂資料庫,並跑一個while迴圈,阻止下次播放與上次一樣,然而在丟到playSong去處理。

若是重複播放,則就在playSong()做囉,而且我的playIndex不會+1,代表是原來的曲目位置繼續在播放。

感想

這個功能真的花了我大把時間在研究,尤其網路上充斥著Object-C的文章,有的還不能用,所以需要去研究更多別人的作法,不過很有趣,歡迎大家分享與指教。

成果分享

鎖定畫面
在控制中心
投射至Apple TV播放

Github

--

--