在前文中,我介绍了如何使用 AVFoundation 框架来制作一个简单的音频播放器(点击查看)。但这个播放器不支持后台播放,程序退到后台时音乐就会停止播放。 本文接着介绍如何实现后台播放功能。
1,效果图
- (1)运行程序并播放音乐。这时我们返回桌面或者关闭屏幕,会发现音乐仍然在播放。
- (2)在锁屏界面上,会显示当前的歌曲信息、专辑图片、当前进度等。同时还提供相关的控制按钮供我们使用。
- (3)同样的,在上拉的音乐控制面板中,也会显示相关信息,并允许我们进行相关操作。

2,实现步骤
(1)为了让播放器能在后台持续播放,我们需要将 Targets -> Capabilities ->BackgroundModes 设为 ON,同时勾选“Audio, AirPlay, and Picture in Picture”。

(2)同时还要在 AppDelegate.swift 中注册后台播放。
import UIKit
import AVFoundation
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions
launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
// 注册后台播放
let session = AVAudioSession.sharedInstance()
do {
try session.setActive(true)
try session.setCategory(AVAudioSessionCategoryPlayback)
} catch {
print(error)
}
return true
}
func applicationWillResignActive(_ application: UIApplication) {
}
func applicationDidEnterBackground(_ application: UIApplication) {
}
func applicationWillEnterForeground(_ application: UIApplication) {
}
func applicationDidBecomeActive(_ application: UIApplication) {
}
func applicationWillTerminate(_ application: UIApplication) {
}
}
(3)ViewController.swift(主视图代码,黄色部分为新增的代码)
import UIKit
import AVFoundation
import MediaPlayer
class ViewController: UIViewController {
//播放按钮
@IBOutlet weak var playButton: UIButton!
//可拖动的进度条
@IBOutlet weak var playbackSlider: UISlider!
//当前播放时间标签
@IBOutlet weak var playTime: UILabel!
//播放器相关
var playerItem:AVPlayerItem?
var player:AVPlayer?
override func viewDidLoad() {
super.viewDidLoad()
//初始化播放器
let url = URL(string: "http://mxd.766.com/sdo/music/data/3/m10.mp3")
playerItem = AVPlayerItem(url: url!)
player = AVPlayer(playerItem: playerItem!)
//设置进度条相关属性
let duration : CMTime = playerItem!.asset.duration
let seconds : Float64 = CMTimeGetSeconds(duration)
playbackSlider!.minimumValue = 0
playbackSlider!.maximumValue = Float(seconds)
playbackSlider!.isContinuous = false
//播放过程中动态改变进度条值和时间标签
player!.addPeriodicTimeObserver(forInterval: CMTimeMakeWithSeconds(1, 1),
queue: DispatchQueue.main) { (CMTime) -> Void in
if self.player!.currentItem?.status == .readyToPlay && self.player?.rate != 0{
//更新进度条进度值
let currentTime = CMTimeGetSeconds(self.player!.currentTime())
self.playbackSlider!.value = Float(currentTime)
//一个小算法,来实现00:00这种格式的播放时间
let all:Int=Int(currentTime)
let m:Int=all % 60
let f:Int=Int(all/60)
var time:String=""
if f<10{
time="0\(f):"
}else {
time="\(f)"
}
if m<10{
time+="0\(m)"
}else {
time+="\(m)"
}
//更新播放时间
self.playTime!.text=time
//设置后台播放显示信息
self.setInfoCenterCredentials(playbackState: 1)
}
}
}
//播放按钮点击
@IBAction func playButtonTapped(_ sender: Any) {
//根据rate属性判断当天是否在播放
if player?.rate == 0 {
player!.play()
playButton.setTitle("暂停", for: .normal)
} else {
player!.pause()
playButton.setTitle("播放", for: .normal)
//后台播放显示信息进度停止
setInfoCenterCredentials(playbackState: 0)
}
}
//拖动进度条改变值时触发
@IBAction func playbackSliderValueChanged(_ sender: Any) {
let seconds : Int64 = Int64(playbackSlider.value)
let targetTime:CMTime = CMTimeMake(seconds, 1)
//播放器定位到对应的位置
player!.seek(to: targetTime)
//如果当前时暂停状态,则自动播放
if player!.rate == 0
{
player?.play()
playButton.setTitle("暂停", for: .normal)
}
}
//页面显示时添加相关通知监听
override func viewWillAppear(_ animated: Bool) {
//播放完毕
NotificationCenter.default.addObserver(self, selector: #selector(finishedPlaying),
name: NSNotification.Name.AVPlayerItemDidPlayToEndTime, object: playerItem)
//告诉系统接受远程响应事件,并注册成为第一响应者
UIApplication.shared.beginReceivingRemoteControlEvents()
self.becomeFirstResponder()
}
//页面消失时取消歌曲播放结束通知监听
override func viewWillDisappear(_ animated: Bool) {
NotificationCenter.default.removeObserver(self)
//停止接受远程响应事件
UIApplication.shared.endReceivingRemoteControlEvents()
self.resignFirstResponder()
}
//歌曲播放完毕
func finishedPlaying(myNotification:NSNotification) {
print("播放完毕!")
let stopedPlayerItem: AVPlayerItem = myNotification.object as! AVPlayerItem
stopedPlayerItem.seek(to: kCMTimeZero)
}
//是否能成为第一响应对象
override var canBecomeFirstResponder: Bool {
return true
}
// 设置后台播放显示信息
func setInfoCenterCredentials(playbackState: Int) {
let mpic = MPNowPlayingInfoCenter.default()
//专辑封面
let mySize = CGSize(width: 400, height: 400)
let albumArt = MPMediaItemArtwork(boundsSize:mySize) { sz in
return UIImage(named: "cover")!
}
//获取进度
let postion = Double(self.playbackSlider!.value)
let duration = Double(self.playbackSlider!.maximumValue)
mpic.nowPlayingInfo = [MPMediaItemPropertyTitle: "我是歌曲标题",
MPMediaItemPropertyArtist: "hangge.com",
MPMediaItemPropertyArtwork: albumArt,
MPNowPlayingInfoPropertyElapsedPlaybackTime: postion,
MPMediaItemPropertyPlaybackDuration: duration,
MPNowPlayingInfoPropertyPlaybackRate: playbackState]
}
//后台操作
override func remoteControlReceived(with event: UIEvent?) {
guard let event = event else {
print("no event\n")
return
}
if event.type == UIEventType.remoteControl {
switch event.subtype {
case .remoteControlTogglePlayPause:
print("暂停/播放")
case .remoteControlPreviousTrack:
print("上一首")
case .remoteControlNextTrack:
print("下一首")
case .remoteControlPlay:
print("播放")
player!.play()
case .remoteControlPause:
print("暂停")
player!.pause()
//后台播放显示信息进度停止
setInfoCenterCredentials(playbackState: 0)
default:
break
}
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
}