zoukankan      html  css  js  c++  java
  • 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天的时间,粗制滥造 = =!的搞出来个简单版的音乐播放器(希望大家多多指点,供跟我一样刚入行的朋友互相交流,互相学习),如图:

    5537343-4a70c94df477e93b.gif

    效果图.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

  • 相关阅读:
    bzoj2763 [JLOI]飞行路线 分层图最短路
    [模板]分块/可修改莫队 (数颜色种类)
    gcd步数
    洛谷2378 因式分解 字符串
    bzoj1090 字符串折叠
    洛谷1034 NOIP2002 矩形覆盖
    Codeforces#441 Div.2 四*题
    SPFA的小优化
    洛谷1073 NOIP2009 最优贸易
    bzoj2100 [Usaco2010 DEC]Apple Delivery苹果贸易
  • 原文地址:https://www.cnblogs.com/sundaysme/p/10386504.html
Copyright © 2011-2022 走看看