使用AVPlayer 播放在线音频,并实现后台锁屏播放,自动连播

在iOS开发中,AVAudioPlayer不是不能播放在线音频的,要播放在线音频只能用以下几个方法:

  • AVPlayer
  • MPMoviePlayerController 能实现音乐流播(但是此方法苹果在iOS9中已经不推荐使用了)
  • Audioqueue,Audiostream (太复杂)
  • 第三方库AudioStreamer(只能播放在线音频)

所以用AVPlayer来实现在线音频播放是最好的选择;一下就是利用AVPlayer实现音乐流播并实现后台锁屏播放和设置自动连播的大概步骤:

项目源代码:Swift 2.0 版豆瓣电台

初始化

先声明AVplayer和AVPlayerItem的实例:

var musicPlayer:AVPlayer!
var playerItem:AVPlayerItem?

然后在需要的播放音频的地方给它赋值:

self.musicPlayer = AVPlayer()
let playerLayer = AVPlayerLayer(player: self.musicPlayer)
self.view.layer.addSublayer(playerLayer)
let url = NSURL(string: " ")
//替换当前的playerItem
self.musicPlayer.replaceCurrentItemWithPlayerItem(playerItem)
//开始播放
self.musicPlayer.play()

以上就是实现播放在线音频的代码,但是如果要实现后台播放和自动连播的话,还要做以下工作:

自动连播

//当播放完成了之后发送通知,继续播放
NSNotificationCenter.defaultCenter().addObserver(self, selector: "playerItemDidReachEnd:", name: AVPlayerItemDidPlayToEndTimeNotification, object: playerItem)
//没有播放完成的通知
NSNotificationCenter.defaultCenter().addObserver(self, selector: "playerItemDidNotReachEnd", name: AVPlayerItemFailedToPlayToEndTimeNotification, object: playerItem)
//接受到通知后响应的方法
func playerItemDidReachEnd(aNotification:NSNotification){
//自动播放相关
}
func playerItemDidNotReachEnd(aNotification:NSNotification){
//歌曲没有正常播放到结束
}

后台播放

如果是播放本地音频的话,后台播放只要在APP启动时加上:

let session = AVAudioSession.sharedInstance()
try! session.setCategory(AVAudioSessionCategoryPlayback)
try! session.setActive(true)

然后在info.plist文件中添加:

<key>UIBackgroundModes</key>
<array>
<string>audio</string>
</array>

即可。

但是如果每一首歌都要请求网络的话,只做上述工作时行不通的,还要在实例化AVPlayer的地方加上:

//后台播放
var bgTask:UIBackgroundTaskIdentifier = 0
if UIApplication.sharedApplication().applicationState == UIApplicationState.Background {
self.musicPlayer.play()
netMusicIsPlaying = true
let app:UIApplication = UIApplication.sharedApplication()
let newTask:UIBackgroundTaskIdentifier = app.beginBackgroundTaskWithExpirationHandler(nil)
if newTask != UIBackgroundTaskInvalid {
app.endBackgroundTask(bgTask)
}
bgTask = newTask
}else{
self.musicPlayer.play()
netMusicIsPlaying = true
isPlayOffline = false
}

这样才能实现真正的后台播放。

锁屏界面和控制中心添加详情

首先要作以下准备工作

  • 在applicationDidEnterBackground中添加:
    application.beginReceivingRemoteControlEvents()
    
  • 在ViewDidAppear中添加:
UIApplication.sharedApplication().beginReceivingRemoteControlEvents()
self.becomeFirstResponder()
  • 在viewDidDisappear中添加:
UIApplication.sharedApplication().endReceivingRemoteControlEvents()
self.resignFirstResponder()
  • 重写canBecomeFirstResponder方法
override func canBecomeFirstResponder() -> Bool {
return true
}

然后把歌曲详情设置到锁屏界面:

func configNowPlayingInfoCenter(){
if (NSClassFromString("MPNowPlayingInfoCenter") != nil) {
//锁屏界面图片的存储方式
let mArt:MPMediaItemArtwork = MPMediaItemArtwork(image: currentSongPic)
//锁屏界面信息字典
var dic:[String : AnyObject] = [
MPMediaItemPropertyTitle : currentSongTitle,
MPMediaItemPropertyArtist : currentSongArtist,
MPMediaItemPropertyArtwork : mArt
]
//获取当前播放的时间和歌曲总时长
let time = self.musicPlayer.currentTime()
let duration = self.musicPlayer.currentItem!.asset.duration
//把信息传递给锁屏界面
dic.updateValue(NSNumber(double: CMTimeGetSeconds(time)), forKey: MPNowPlayingInfoPropertyElapsedPlaybackTime )
dic.updateValue(NSNumber(double: CMTimeGetSeconds(duration)), forKey: MPMediaItemPropertyPlaybackDuration)
dic.updateValue(NSNumber(float: 1.0), forKey: MPNowPlayingInfoPropertyPlaybackRate)
MPNowPlayingInfoCenter.defaultCenter().nowPlayingInfo = dic
}
}

再处理锁屏和控制中心点击事件:

override func remoteControlReceivedWithEvent(event: UIEvent?) {
if event!.type == UIEventType.RemoteControl {
if event!.subtype == UIEventSubtype.RemoteControlNextTrack {
//下一曲
}else if event!.subtype == UIEventSubtype.RemoteControlPause{
//暂停按钮
}else if event!.subtype == UIEventSubtype.RemoteControlPlay{
//播放按钮
}
}
}

利用KVO监视播放器的缓存状态和播放状态

//先移除观察者
if self.musicPlayer.currentItem != nil {
self.musicPlayer.currentItem?.removeObserver(self, forKeyPath: "status")
self.musicPlayer.currentItem?.removeObserver(self, forKeyPath: "loadedTimeRanges")
}
//添加观察者
playerItem.addObserver(self, forKeyPath: "status", options: .New, context: nil)
playerItem.addObserver(self, forKeyPath: "loadedTimeRanges", options: .New, context: nil)
//播放状态
playerItem.addObserver(self, forKeyPath: "status", options: .New, context: nil)
//缓存进度
playerItem.addObserver(self, forKeyPath: "loadedTimeRanges", options: .New, context: nil)
//属性改变了要实现的方法
override func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [String : AnyObject]?, context: UnsafeMutablePointer<Void>) {
//获取缓存进度
if keyPath == "loadedTimeRanges" {
let array = (object as! AVPlayerItem).loadedTimeRanges
if let range = array.first {
//当前缓存的总时间
let rangeValue = range.CMTimeRangeValue
let duration = rangeValue.duration
//总时间换算成秒数
let loadTime = CMTimeGetSeconds(duration)
}else{
//获取当前播放状态
}
}

获取到了缓存时间可以判断当前的缓存时间和当前播放时间的值,来判断网络状况,同时也可以在做视频播放器时用来作缓存条