1. 程式人生 > >Swift 3 :基於 AVAudioPlayer 的簡單音樂播放器

Swift 3 :基於 AVAudioPlayer 的簡單音樂播放器

vat content 控制器 ets 而已 rand init pla pause

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 的簡單音樂播放器