學(xué)習(xí)ios以來(lái)差不多接近兩個(gè)月了绍填,作為一個(gè)剛?cè)胄械牟穗u終于鼓起勇氣寫博客并發(fā)布出來(lái),本周課程講到了ios多媒體應(yīng)用關(guān)于音頻播放這部分(本菜還在讀大學(xué)走的移動(dòng)端ios方向= =)盾饮,課下作業(yè)老師讓做個(gè)基于AVAudioPlayer的音樂(lè)播放器裳瘪,問(wèn)題不大细办,比較簡(jiǎn)單橙凳,想給自己加點(diǎn)難度蕾殴,最近swift這么火,干脆用swift寫個(gè)吧岛啸!于是逼著自己一點(diǎn)點(diǎn)的邊做邊啃swift語(yǔ)法钓觉,花了差不多3天的時(shí)間,粗制濫造 = =坚踩!的搞出來(lái)個(gè)簡(jiǎn)單版的音樂(lè)播放器(希望大家多多指點(diǎn)荡灾,供跟我一樣剛?cè)胄械呐笥鸦ハ嘟涣鳎ハ鄬W(xué)習(xí))瞬铸,如圖:
1.界面
最初想的是該怎么入門批幌,畢竟swift認(rèn)識(shí)我,我不認(rèn)識(shí)它吧そ凇荧缘!還是去世界上最大的同性交友網(wǎng)站GitHub上去看看有沒(méi)有關(guān)于swift播放器的demo把,從學(xué)習(xí)別人的代碼來(lái)入門拦宣,結(jié)果轉(zhuǎn)了一大圈都沒(méi)有找到合適的截粗,不過(guò)發(fā)現(xiàn)了個(gè)有四百個(gè)star的提供炫酷手勢(shì)界面殼子的項(xiàng)目,但僅僅是個(gè)殼子而已鸵隧,好吧我的界面就用它了绸罗,對(duì)了說(shuō)到界面,他界面用的是StoryBoard還用到了個(gè)新控件叫ContainerView豆瘫,ContainerView是用來(lái)在一個(gè)視圖控制器上添加子視圖控制器的珊蟀,他這樣做的好處就是可以讓迷你播放欄一只處于界面最上方,點(diǎn)擊tabbar切換界面的時(shí)候外驱,只需要將ContainerView里的子視圖控制器更換即可育灸。具體用法大家可以自行簡(jiǎn)書(shū)。
2.獲取音樂(lè)及相關(guān)信息略步,封裝音樂(lè)播放類
界面有了描扯,使用AVAudioPlayer根據(jù)本地音樂(lè)的路徑進(jìn)行音樂(lè)播放定页,在這里我封裝了個(gè)方法用來(lái)獲取本地文件夾myMusic里所有歌曲路徑以及作家趟薄,封面,歌曲名等典徊。為了做出上一曲下一曲功能我將每首歌曲都編了號(hào)杭煎。
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// 這里是個(gè)坑坑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!
}
}
一個(gè)音樂(lè)播放器每次播放都只能放一首歌,于是我將其封裝成了一個(gè)AudioPlayer單例類卒落,其實(shí)也不能算單例羡铲,應(yīng)該叫工具類比較合適。
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
}
//進(jìn)度條相關(guān)
static func progress()->Double{
return (instance?.currentTime)!/(instance?.duration)!
}
static func musicDuration()->Double{
return (instance?.duration)!
}
static func currentTime()->Double{
return (instance?.currentTime)!
}
//當(dāng)前播放的音樂(lè)
static func activeSong()->Music?{
return activeMusic
}
//是否在播放音樂(lè)
static func isPlaying()->Bool{
return (instance?.isPlaying)!
}
//隨機(jī)播放
static func musicRandomPlay()->Bool{
if isRandomPlay==false{
isRandomPlay=true
return isRandomPlay
}else{
isRandomPlay=false
return isRandomPlay
}
}
}
3.音樂(lè)播放
所有歌曲及其信息都能拿到了儡毕,展示在tableView上我就不用多說(shuō)也切,播放音樂(lè)也只需要在tableView的相應(yīng)點(diǎn)擊方法里使用AudioPlayer類的share方法將對(duì)應(yīng)的歌曲model傳入即可扑媚,這里有個(gè)如何讓miniplayer展示所點(diǎn)擊歌曲的問(wèn)題,我是將tableView放進(jìn)ContainerView雷恃,tableView相當(dāng)于主視圖的子視圖疆股,而miniplayer是在主視圖里,這里就要用到子視圖向父視圖回傳值的方法倒槐,在這里我是采用的閉包回傳旬痹,其實(shí)也不需要傳什么值,只需要讓主視圖知道子視圖被點(diǎn)擊了即可讨越,因?yàn)槲铱梢酝ㄟ^(guò)AudioPlayer類來(lái)得到當(dāng)前播放的歌曲信息两残。swift里的閉包傳值在語(yǔ)法上和oc的區(qū)別也是很大,花了我不少時(shí)間- -
子視圖里:
var musicPlayByTableViewCell: ((Int) -> Void)//聲明
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
musicPlayByTableViewCell?(indexPath.row)
tableView.deselectRow(at: indexPath, animated: true)//取消被選中狀態(tài)
}
主視圖里:
self.musicTalbleVC.musicPlayByTableViewCell={
[weak self] musicNum in self?.musicModel=self?.musicArry[musicNum]
self?.nowNum=musicNum
self?.miniPlayerWork()
}
3.1界面功能及相關(guān)細(xì)節(jié)
音樂(lè)播放時(shí)的界面也就是幾個(gè)button和常見(jiàn)的視圖控件把跨,說(shuō)說(shuō)進(jìn)度條人弓,AVAudioPlayer類提供了當(dāng)前進(jìn)度,以及總時(shí)長(zhǎng)着逐,所以我是用定時(shí)器每一秒獲取一次當(dāng)前進(jìn)度的方法票从,再當(dāng)前進(jìn)度除以總進(jìn)度使用即可,上下曲因?yàn)槲以讷@取所有歌曲時(shí)就將每首歌曲編了號(hào)滨嘱,上下曲就只需要將編號(hào)加一或減一就可以了峰鄙,包括隨機(jī)播放也只需要使用隨機(jī)函數(shù)隨機(jī)總歌曲編號(hào)就行了,還有些界面上的細(xì)節(jié)太雨,在詳細(xì)歌曲播放界面點(diǎn)擊了暫停吟榴,回到主視圖時(shí)miniplayer的圖標(biāo)及相關(guān)信息也應(yīng)該與其同步哦。tableView列表上也將當(dāng)前播放的歌曲進(jìn)行了高亮展示(加了歌活躍標(biāo)記)囊扳,在這里吩翻,我在musicmodel里添加了IsActive屬性,并在tableView的Cell里進(jìn)行了判斷锥咸,如果cell展示的歌曲是當(dāng)前正在播放的歌曲isActive為true展示活躍標(biāo)記狭瞎。還有音樂(lè)播放完畢后需要自動(dòng)播放下一曲哦,這里我是用的定時(shí)器控件搏予,讀取歌曲進(jìn)度進(jìn)度完了就播放下一曲(相當(dāng)點(diǎn)擊了一次下一曲按鈕)
3.12tabBar控制界面
在storyBoard上是直接拖的TabBar熊锭,和直接用tabBarController大不一樣,點(diǎn)擊不同的圖標(biāo)響應(yīng)不同的事件ContainerView里的視圖控制器也就相應(yīng)的變化雪侥,關(guān)于如何改變ContainerView里的視圖控制器我也是找了好久碗殷,還比較復(fù)雜,但確實(shí)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音樂(lè)的后臺(tái)播放以及鎖屏和上拉欄展示音樂(lè)信息
音樂(lè)的后臺(tái)播放,因?yàn)槲疫€從為接觸過(guò)應(yīng)用后臺(tái)運(yùn)行這一塊旬牲,所以我也只是參照網(wǎng)上的代碼將功能做出來(lái)了而已仿粹,還有不完善的地方搁吓,后臺(tái)播放中斷(如打電話)后不會(huì)恢復(fù)播放。上拉欄展示音樂(lè)信息以及之中的按鈕觸發(fā)方法吭历,都是系統(tǒng)封裝好了的 直接用就是了擎浴。
話說(shuō)很多警告呀!
func setBackground() {
//大標(biāo)題 - 小標(biāo)題 - 歌曲總時(shí)長(zhǎng) - 歌曲當(dāng)前播放時(shí)長(zhǎng) - 封面
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 { // 自動(dòng)播放下一曲 ()后臺(tái)
self.nowNum=self.nowNum+1
if self.nowNum>=self.musicArry.count{
self.nowNum=0
}
if AudioPlayer.nextsong(num: self.nowNum){
refreashView()
}
}
}
4.最后
這是剛?cè)胄械牟穗u的第一篇博客毒涧,肯定有很多不妥當(dāng)?shù)牡胤街ぃM蠹乙?jiàn)諒,項(xiàng)目也沒(méi)做多久契讲,對(duì)swift的理解還很淺仿吞,肯定不如大大們的法眼,我也只希望通過(guò)寫這篇博客捡偏,總結(jié)一下我學(xué)到的知識(shí)唤冈,分享出來(lái),大家互相交流學(xué)習(xí)银伟。