Swift 3 :基於 AVAudioPlayer 的簡單音樂播放器
2017.05.22 17:46* 字數 1585 閱讀 5095評論 0喜歡 8贊賞 2
https://www.jianshu.com/p/4d5c257428a1
學習ios以來差不多接近兩個月了,作為一個剛入行的菜雞終於鼓起勇氣寫博客並發布出來,本周課程講到了ios多媒體應用關於音頻播放這部分(本菜還在讀大學走的移動端ios方向= =),課下作業老師讓做個基於AVAudioPlayer的音樂播放器,問題不大,比較簡單,想給自己加點難度,最近swift這麽火,幹脆用swift寫個吧!於是逼著自己一點點的邊做邊啃swift語法,花了差不多3天的時間,粗制濫造 = =!的搞出來個簡單版的音樂播放器(希望大家多多指點,供跟我一樣剛入行的朋友互相交流,互相學習),如圖:
效果圖.gif
1.界面
最初想的是該怎麽入門,畢竟swift認識我,我不認識它啊!還是去世界上最大的同性交友網站GitHub上去看看有沒有關於swift播放器的demo把,從學習別人的代碼來入門,結果轉了一大圈都沒有找到合適的,不過發現了個有四百個star的提供炫酷手勢界面殼子的項目,但僅僅是個殼子而已,好吧我的界面就用它了,對了說到界面,他界面用的是StoryBoard還用到了個新控件叫ContainerView,ContainerView是用來在一個視圖控制器上添加子視圖控制器的,他這樣做的好處就是可以讓迷你播放欄一只處於界面最上方,點擊tabbar切換界面的時候,只需要將ContainerView裏的子視圖控制器更換即可。具體用法大家可以自行簡書。
2.獲取音樂及相關信息,封裝音樂播放類
界面有了,使用AVAudioPlayer根據本地音樂的路徑進行音樂播放,在這裏我封裝了個方法用來獲取本地文件夾myMusic裏所有歌曲路徑以及作家,封面,歌曲名等。為了做出上一曲下一曲功能我將每首歌曲都編了號。
static func getALL()->Array<Music>{
if musicArry == nil {
var musicArryList=Array<Music>()
var fileArry:[String]?
var num:Int=0;
let path=Bundle.main.path(forResource:"mymusic", ofType: nil)
do{
try fileArry=FileManager.default.contentsOfDirectory(atPath: path!)
}
catch{
print("error")
}
for n in fileArry! {
let singlePath=path!+"/"+n
let avURLAsset = AVURLAsset(url: URL.init(fileURLWithPath: singlePath))
let musicModel:Music = Music()
for i in avURLAsset.availableMetadataFormats {
for j in avURLAsset.metadata(forFormat: i) {
//歌曲名
if j.commonKey == "title"{
musicModel.musicName = j.value as? String
}//封面圖片
if j.commonKey == "artwork"{
musicModel.musicimg=j.value as? Data// 這裏是個坑坑T T
}//專輯名
if j.commonKey == "albumName"{
musicModel.musicAlbum=j.value as? String
}
//歌手
if j.commonKey == "artist"{
musicModel.musicAuthor=j.value as? String
}
}
}
musicModel.musicURL=URL.init(fileURLWithPath: singlePath)
num += 1
musicModel.musicNum=num;
musicArryList.append(musicModel)
}
musicArry=musicArryList
return musicArry!
}else{
return musicArry!
}
}
一個音樂播放器每次播放都只能放一首歌,於是我將其封裝成了一個AudioPlayer單例類,其實也不能算單例,應該叫工具類比較合適。
import UIKit
import AVFoundation
final class AudioPlayer: NSObject {
private static var instance: AVAudioPlayer? = nil //static 直到被銷毀 全局存在
private static var activeMusic:Music?=nil
private static var isRandomPlay=false
static func share(model:Music) -> Bool {
do{
try instance = AVAudioPlayer(contentsOf: model.musicURL!)
}
catch{
instance=nil;
print("error")
return false;
}
instance?.play()
activeMusic=model
return true
}
//停止
static func stop(){
instance?.stop()
}
//播放
static func play()->Bool{
if (instance?.isPlaying)! {
instance?.pause()
return false
}else{
instance?.play()
return true
}
}
//暫停
static func pause(){
instance?.pause()
}
//下一曲
static func nextsong( num:Int)->Bool{
var num = num
var musicArry:Array<Music>!
musicArry=Music.getALL()
if isRandomPlay{
num = Int(arc4random_uniform(UInt32(musicArry.count-1)))
}
if(share(model: musicArry[num])){
return true
}else{
return false
}
}
//上一曲
static func prevsong(num:Int)->Bool{
var num = num
var musicArry:Array<Music>!
musicArry=Music.getALL()
if isRandomPlay{
num = Int(arc4random_uniform(UInt32(musicArry.count-1)))
}
if(share(model: musicArry[num])){
return true
}else{
return false
}
}
//聲音控制
static func voice(num:Float){
instance?.volume=num
}
//進度條相關
static func progress()->Double{
return (instance?.currentTime)!/(instance?.duration)!
}
static func musicDuration()->Double{
return (instance?.duration)!
}
static func currentTime()->Double{
return (instance?.currentTime)!
}
//當前播放的音樂
static func activeSong()->Music?{
return activeMusic
}
//是否在播放音樂
static func isPlaying()->Bool{
return (instance?.isPlaying)!
}
//隨機播放
static func musicRandomPlay()->Bool{
if isRandomPlay==false{
isRandomPlay=true
return isRandomPlay
}else{
isRandomPlay=false
return isRandomPlay
}
}
}
3.音樂播放
所有歌曲及其信息都能拿到了,展示在tableView上我就不用多說,播放音樂也只需要在tableView的相應點擊方法裏使用AudioPlayer類的share方法將對應的歌曲model傳入即可,這裏有個如何讓miniplayer展示所點擊歌曲的問題,我是將tableView放進ContainerView,tableView相當於主視圖的子視圖,而miniplayer是在主視圖裏,這裏就要用到子視圖向父視圖回傳值的方法,在這裏我是采用的閉包回傳,其實也不需要傳什麽值,只需要讓主視圖知道子視圖被點擊了即可,因為我可以通過AudioPlayer類來得到當前播放的歌曲信息。swift裏的閉包傳值在語法上和oc的區別也是很大,花了我不少時間- -
子視圖裏:
var musicPlayByTableViewCell: ((Int) -> Void)//聲明
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
musicPlayByTableViewCell?(indexPath.row)
tableView.deselectRow(at: indexPath, animated: true)//取消被選中狀態
}
主視圖裏:
self.musicTalbleVC.musicPlayByTableViewCell={
[weak self] musicNum in self?.musicModel=self?.musicArry[musicNum]
self?.nowNum=musicNum
self?.miniPlayerWork()
}
3.1界面功能及相關細節
音樂播放時的界面也就是幾個button和常見的視圖控件,說說進度條,AVAudioPlayer類提供了當前進度,以及總時長,所以我是用定時器每一秒獲取一次當前進度的方法,再當前進度除以總進度使用即可,上下曲因為我在獲取所有歌曲時就將每首歌曲編了號,上下曲就只需要將編號加一或減一就可以了,包括隨機播放也只需要使用隨機函數隨機總歌曲編號就行了,還有些界面上的細節,在詳細歌曲播放界面點擊了暫停,回到主視圖時miniplayer的圖標及相關信息也應該與其同步哦。tableView列表上也將當前播放的歌曲進行了高亮展示(加了歌活躍標記),在這裏,我在musicmodel裏添加了IsActive屬性,並在tableView的Cell裏進行了判斷,如果cell展示的歌曲是當前正在播放的歌曲isActive為true展示活躍標記。還有音樂播放完畢後需要自動播放下一曲哦,這裏我是用的定時器控件,讀取歌曲進度進度完了就播放下一曲(相當點擊了一次下一曲按鈕)
3.12tabBar控制界面
在storyBoard上是直接拖的TabBar,和直接用tabBarController大不一樣,點擊不同的圖標響應不同的事件ContainerView裏的視圖控制器也就相應的變化,關於如何改變ContainerView裏的視圖控制器我也是找了好久,還比較復雜,但確實ContainerView很好用啊!
ContainerView:更改視圖控制器
func tabBar(_ tabBar: UITabBar, didSelect item: UITabBarItem) {
if item.tag == 1 {
let newController = self.musicTalbleVC!
let oldController = childViewControllers.last!
if newController != oldController {
// self.setStatusBarBackgroundColor(color: UIColor.white)
oldController.willMove(toParentViewController: nil)
addChildViewController(newController)
newController.view.frame = oldController.view.frame
//isAnimating = true
transition(from: oldController, to: newController, duration: 0.5, options: UIViewAnimationOptions.transitionFlipFromLeft, animations: nil, completion: { (finished) -> Void in
oldController.removeFromParentViewController()
newController.didMove(toParentViewController: self) //self.isAnimating = false
})
}
}else{
let newController = self.moreVC!
let oldController = childViewControllers.last!
if newController != oldController {
self.setStatusBarBackgroundColor(color: UIColor.clear)
oldController.willMove(toParentViewController: nil)
addChildViewController(newController)
newController.view.frame = oldController.view.frame
transition(from: oldController, to: newController, duration: 0.5, options: UIViewAnimationOptions.transitionFlipFromRight, animations: nil, completion: { (finished) -> Void in
oldController.removeFromParentViewController()
newController.didMove(toParentViewController: self) //self.isAnimating = false
})
}
}
}
3.13音樂的後臺播放以及鎖屏和上拉欄展示音樂信息
音樂的後臺播放,因為我還從為接觸過應用後臺運行這一塊,所以我也只是參照網上的代碼將功能做出來了而已,還有不完善的地方,後臺播放中斷(如打電話)後不會恢復播放。上拉欄展示音樂信息以及之中的按鈕觸發方法,都是系統封裝好了的 直接用就是了。
話說很多警告呀!
func setBackground() {
//大標題 - 小標題 - 歌曲總時長 - 歌曲當前播放時長 - 封面
self.musicModel=AudioPlayer.activeSong()
var settings = [MPMediaItemPropertyTitle: self.musicModel?.musicName,
MPMediaItemPropertyArtist: self.musicModel?.musicAuthor ,
MPMediaItemPropertyPlaybackDuration: "\(AudioPlayer.musicDuration())",
MPNowPlayingInfoPropertyElapsedPlaybackTime: "\(AudioPlayer.currentTime())",MPMediaItemPropertyArtwork: MPMediaItemArtwork.init(image: UIImage (data: (self.musicModel?.musicimg)!)!)] as [String : Any]
MPNowPlayingInfoCenter.default().setValue(settings, forKey: "nowPlayingInfo")
if AudioPlayer.progress() > 0.99 { // 自動播放下一曲 ()後臺
self.nowNum=self.nowNum+1
if self.nowNum>=self.musicArry.count{
self.nowNum=0
}
if AudioPlayer.nextsong(num: self.nowNum){
refreashView()
}
}
}
4.最後
這是剛入行的菜雞的第一篇博客,肯定有很多不妥當的地方,希望大家見諒,項目也沒做多久,對swift的理解還很淺,肯定不如大大們的法眼,我也只希望通過寫這篇博客,總結一下我學到的知識,分享出來,大家互相交流學習。
附源碼 https://github.com/calvinWen/SwiftMusicPlayer
Swift 3 :基於 AVAudioPlayer 的簡單音樂播放器