美文网首页
AVFoundation(一) AVPlayer音乐播放器

AVFoundation(一) AVPlayer音乐播放器

作者: NewSongs | 来源:发表于2017-12-12 15:09 被阅读79次

    欢迎小伙伴的到来~~~

    本次的代码已经上传GitHub

    (一)AVFoundation简介

    AVFoundation框架将四大技术领域结合在一起,涵盖了在苹果平台上捕捉,处理,合成,控制,导入和导出视听媒体的广泛任务。

    这篇文章将为大家简单总结一下AVFoundation框架中的AVPlayer,并使用AVPlayer创建一个播放器。

    (二) AVPlayer

    1、介绍

    AVPlayer是用于管理媒体资产的回放和定时的控制器对象。您可以使用一个AVPlayer播放本地和远程基于文件的媒体,如QuickTime电影和MP3音频文件,以及使用HTTP Live Streaming提供的视听媒体。

    AVPlayer一次播放单个媒体资源。

    创建和管理媒体资产的队列使用AVPlayerAVQueuePlayer(本章不对AVPlayerAVQueuePlayer进行介绍)

    2、使用AVPlayer

    2.1初始化

    init()、init(url: URL)

    init(playerItem: AVPlayerItem?):使用一个AVPlayerItem进行初始化播放器

    AVPlayerItem用于模拟播放器播放资产的时间和呈现状态

    AVPlayerItem使用init(url: URL)、init(asset: AVAsset)初始化

    AVAsset定义组成资产轨道的集合性质(可以访问表示集合轨迹的实例,如果需要可以独立检查这些实例)以后的文章有机会详细介绍AVAsset

    AVPlayer一次只能播放单个媒体资源,更换新的播放使用replaceCurrentItem(with:)方法

        //初始化
        self.player = AVPlayer()
        self.player = AVPlayer(url: URL(string: "url")!)
        
        //let item = AVPlayerItem(asset: AVAsset(url: URL(string: "url")!))
        let item = AVPlayerItem(url: URL(string: "url")!)
        self.player = AVPlayer(playerItem: item)
        
        //AVPlayer一次播放单个媒体资源,使用replaceCurrentItem设置新的播放
        self.player.replaceCurrentItem(with: playerItem)
    

    2.2对播放进行控制

        //AVPlayer没有停止方法,只要播放和暂停
        self.player.play()
        self.player.pause()
        
        //更换播放项目,AVPlayer每次只能播放一个媒体
        self.player.replaceCurrentItem(with: item)
        
        //获取当前正在播放项目
        self.player.currentTime
        
        //播放速率,速率为0时表示已经播放已经暂停或停止
        self.player.rate = 1.0
        
        //跳转到指定播放位置
        self.player.seek(to: CMTime(value: 1, timescale: 1))
        
        //添加视频Layer层(不是必须添加)用于显示视频图像
        let playerLayer = AVPlayerLayer(player: self.player)
        playerLayer.frame = self.view.bounds
        self.view.layer.addSublayer(playerLayer)
    

    self.player.seek(to: CMTime(value: 1, timescale: 1))//跳转到指定播放位置

    CMTime的出现是为了解决浮点数表示的时间计算过程中出现误差的问题,在AVFoundation中很多类都使用CMTime来表示时间,CMTime的结构如下

    //typedef int64_t CMTimeValue
    //typedef int32_t CMTimeScale
    
    typedef struct
         CMTimeValue    value;      
         CMTimeScale    timescale;  
         CMTimeFlags    flags;      
         CMTimeEpoch    epoch;      
       } CMTime;
    

    这里只对value和timescale两个参数进行简单介绍,更为详细的介绍可以参考这篇文章《理解CMTime》

    timescale表示一秒被分成的片段数量,value表示片段总量。

    例如CMTime(value: 1, timescale: 2) timescale将一秒分成两份,value有一个片段,这样这个CMTime表示的就是0.5秒的位置。以此推算CMTime(value: 10, timescale: 10)这个就表示1秒的位置

    3、获取播放时间、播放状态

    3.1监听播放状态

    通过监听AVPlayerItem中的一些属性来获得来得到当前播放媒体的状态。

            //状态监听
            playerItem.addObserver(self,
                                   forKeyPath: #keyPath(AVPlayerItem.status),
                                   options: [.new],
                                   context: nil)
            //缓冲时间监听
            playerItem.addObserver(self,
                                   forKeyPath: #keyPath(AVPlayerItem.loadedTimeRanges),
                                   options: [.new],
                                   context: nil)
            
            //缓冲不足暂停
            playerItem.addObserver(self,
                                   forKeyPath: #keyPath(AVPlayerItem.isPlaybackBufferEmpty),
                                   options: [.new],
                                   context: nil)
            
            //缓冲可以跟上播放
            playerItem.addObserver(self,
                                   forKeyPath: #keyPath(AVPlayerItem.isPlaybackLikelyToKeepUp),
                                   options: [.new],
                                   context: nil)
            
            //缓冲完成
            playerItem.addObserver(self,
                                   forKeyPath: #keyPath(AVPlayerItem.isPlaybackBufferFull),
                                   options: [.new],
                                   context: nil)
                                   
    // MARK: - AVPlayerItem Observer
        override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
            
            if keyPath == #keyPath(AVPlayerItem.status) {
                
                //状态监听
                let status: AVPlayerItemStatus
                
                if let statusNumber = change?[.newKey] as? NSNumber {
                    status = AVPlayerItemStatus(rawValue: statusNumber.intValue)!
                } else {
                    status = .unknown
                }
                
                switch status {
                    
                case .readyToPlay:
                    //可以开始播放了
                    
                    self.player.play()
                    break
                    
                case .failed:
                    //播放失败
                    print("error:\(String(describing: self.player.error))")
                    break
                    
                case .unknown:
                    // 未知错误
                    break
                }
            } else if keyPath == #keyPath(AVPlayerItem.loadedTimeRanges) {
                
                //缓冲时间监听
    
            } else if keyPath == #keyPath(AVPlayerItem.isPlaybackBufferEmpty) {
                
                //缓冲不足暂停
    
            } else if keyPath == #keyPath(AVPlayerItem.isPlaybackLikelyToKeepUp) {
                
                //缓冲可以跟上播放
    
            } else if keyPath == #keyPath(AVPlayerItem.isPlaybackBufferFull) {
                
                //缓冲完成
            }
        }
    
    3.2播放时间监听
        //添加周期时间观测器
        let interval = CMTime(value: 1, timescale: 1)
        self.player?.addPeriodicTimeObserver(forInterval: interval, queue: nil)
        { (time) in
            let currentItemTime = CMTimeGetSeconds(time)            
        }
    
    3.3 AVPlayerItem相关通知
        //AVPlayerItem播放到结束通知
        NotificationCenter.default.addObserver(self, selector: #selector(playerItemDidPlayToEndTime(_:)), name: NSNotification.Name.AVPlayerItemDidPlayToEndTime, object: nil)
                    
        //AVPlayerItem媒体没有及时到达制定播放位置
        NotificationCenter.default.addObserver(self, selector: #selector(playerItemPlaybackStalled(_:)), name: NSNotification.Name.AVPlayerItemPlaybackStalled, object: nil)
                    
        //AVPlayerItem添加了一个新的错误日志条目
        NotificationCenter.default.addObserver(self, selector: #selector(playerItemNewErrorLogEntry(_:)), name: NSNotification.Name.AVPlayerItemNewErrorLogEntry, object: nil)
        
        /*
        //其他通知
        AVF_EXPORT NSString *const AVPlayerItemTimeJumpedNotification             NS_AVAILABLE(10_7, 5_0);    //该项目的当前时间改变了不连续
        
        AVF_EXPORT NSString *const AVPlayerItemDidPlayToEndTimeNotification      NS_AVAILABLE(10_7, 4_0);   //项目已发挥到其结束时间
        
        AVF_EXPORT NSString *const AVPlayerItemFailedToPlayToEndTimeNotification NS_AVAILABLE(10_7, 4_3);   //项目未能发挥其结束时间。
        
        AVF_EXPORT NSString *const AVPlayerItemPlaybackStalledNotification       NS_AVAILABLE(10_9, 6_0);    //媒体没有及时到达继续播放。
        
        AVF_EXPORT NSString *const AVPlayerItemNewAccessLogEntryNotification     NS_AVAILABLE(10_9, 6_0);    //添加了一个新的访问日志条目。
        
       AVF_EXPORT NSString *const AVPlayerItemNewErrorLogEntryNotification         NS_AVAILABLE(10_9, 6_0);    //添加了一个新的错误日志条目。
                     
       // notification userInfo key                                                                    type
        AVF_EXPORT NSString *const AVPlayerItemFailedToPlayToEndTimeErrorKey     NS_AVAILABLE(10_7, 4_3);   // NSError
        */
    

    4、设置后台播放

    开启后台播放.png
    //设置后台播放
        do {
            let session = AVAudioSession.sharedInstance()
            //设置后台播放
            try session.setCategory(AVAudioSessionCategoryPlayback)
            //设置活跃(true静音状态下有声音)
            try session.setActive(true)
        } catch let error as NSError {
            print(error)
        }
    

    当播放为视频时进入后台会自动暂停,我暂时想到的解决办法是进入后台后手动进行一下play(),代码如下

        // MARK: - 应用进入后台通知
        @objc func applicationEnterBackground(_ notification: NSNotification) {
            
            //处理视频播放中进入后台自动暂停的问题
            weak var weakSelf = self
            DispatchQueue.main.asyncAfter(deadline: DispatchTime.now()+0.2) {
                if weakSelf.isPlayStatus == false{
                    weakSelf?.play()
                }
            }
        }
    

    5、中断和线路改变通知

        //添加中断通知(被电话或者其他事件打断播放)
        NotificationCenter.default.addObserver(self, selector: #selector(handleInterruption(_:)), name: NSNotification.Name.AVAudioSessionInterruption, object:nil)
            
        //添加线路改变通知
        NotificationCenter.default.addObserver(self, selector: #selector(handleRouteChange(_:)), name: NSNotification.Name.AVAudioSessionRouteChange, object: nil)
        
        
        //MARK: - 中断通知
        @objc func handleInterruption(_ notification: NSNotification) {
            
            let info = notification.userInfo;
            let type = AVAudioSessionInterruptionType(rawValue: info?[AVAudioSessionInterruptionTypeKey] as! UInt)
            
            //中断开始,被打断
            if type == AVAudioSessionInterruptionType.began {
                
                self.pause()
            } else {//中断结束
                
                let optionType = AVAudioSessionInterruptionOptions(rawValue: info?[AVAudioSessionInterruptionOptionKey] as! UInt)
                if optionType == AVAudioSessionInterruptionOptions.shouldResume {
                    //应用获得可以继续播放通知,应该恢复播放
                    self.play()
                }
            }
        }
        
        //MARK: - 线路切换通知
        @objc func handleRouteChange(_ notification: NSNotification) {
            
            let info = notification.userInfo;
            let reason = AVAudioSessionRouteChangeReason(rawValue: info?[AVAudioSessionRouteChangeReasonKey] as! UInt)
            
            if reason == AVAudioSessionRouteChangeReason.oldDeviceUnavailable {
                //旧设备不可用时
                
                
                //获取上一个输出设备
                let previousRoute = (info?[AVAudioSessionRouteChangePreviousRouteKey] as! AVAudioSessionRouteDescription)
                let previousOutput = previousRoute.outputs[0]
                let portType = previousOutput.portType
                
                //如果是断开耳机连接
                if (portType.elementsEqual(AVAudioSessionPortHeadphones)) {
                    self.pause()
                }
            }
            
        }
    

    本次的代码已经上传GitHub, 代码中我还加入了接收RemoteControl遥控(耳机线控和锁屏控制)和锁屏歌曲信息的显示等功能,实现了简单的一个播放器功能,欢迎小伙伴点击GitHub下载

    愿一切安好

    相关文章

      网友评论

          本文标题:AVFoundation(一) AVPlayer音乐播放器

          本文链接:https://www.haomeiwen.com/subject/yjhnixtx.html