写在开头
一、后台权限申请
1、在Info.plist文件中里设置选项Required background modes ,然后添加item0:App plays audio or streams audio/video using AirPlay
2、设置Capabilities -> Background Modes -> 勾选 Audio,AirPlay,and Picture in Picture
3、在AppDelegate.m中添加代码
- (void)applicationWillResignActive:(UIApplication *)application {
// *让app接受远程事件控制,及锁屏是控制版会出现播放按钮
[[UIApplication sharedApplication] beginReceivingRemoteControlEvents];
// *后台播放代码
AVAudioSession*session=[AVAudioSession sharedInstance];
[session setActive:YES error:nil];
[session setCategory:AVAudioSessionCategoryPlayback error:nil];
}
二、添加控制代码
我们在这里使用MPRemoteCommandCenter添加远程控制代码,使用MPNowPlayingInfoCenter更新通知中心控制台的媒体信息。
关于这个两个控件的使用,可以在网上查询一些教程,我这里做简单的介绍,并添加一下使用过程中需要的注意事项
(1)是一个响应系统外部附件(耳机)以及系统控件发出的运程控制事件对象。
(2)在使用的过程中,不可以多次调用(最好只调用一次,主要是addTarget不能多次调用),因为调用多次以后,会多次响应远程事件
- (void)setupLockScreenControlInfo {
MPRemoteCommandCenter *commandCenter = [MPRemoteCommandCenter sharedCommandCenter];
// 锁屏播放
MPRemoteCommand *playCommand = commandCenter.playCommand;
playCommand.enabled = YES;
[playCommand addTargetWithHandler:^MPRemoteCommandHandlerStatus(MPRemoteCommandEvent * _Nonnull event) {
NSLog(@"锁屏暂停后点击播放");
if (!self.isPlaying) {
[self playMusic];
}
return MPRemoteCommandHandlerStatusSuccess;
}];
// 锁屏暂停
MPRemoteCommand *pauseCommand = commandCenter.playCommand;
pauseCommand.enabled = YES;
[pauseCommand addTargetWithHandler:^MPRemoteCommandHandlerStatus(MPRemoteCommandEvent * _Nonnull event) {
NSLog(@"锁屏正在播放点击后暂停");
if (self.isPlaying) {
[self pauseMusic];
}
return MPRemoteCommandHandlerStatusSuccess;
}];
MPRemoteCommand *stopCommand = commandCenter.playCommand;
stopCommand.enabled = YES;
[stopCommand addTargetWithHandler:^MPRemoteCommandHandlerStatus(MPRemoteCommandEvent * _Nonnull event) {
[self stopMusic];
return MPRemoteCommandHandlerStatusSuccess;
}];
// 播放和暂停按钮(耳机控制)
MPRemoteCommand *playPauseCommand = commandCenter.togglePlayPauseCommand;
playPauseCommand.enabled = YES;
[playPauseCommand addTargetWithHandler:^MPRemoteCommandHandlerStatus(MPRemoteCommandEvent * _Nonnull event) {
if (self.isPlaying) {
[self pauseMusic];
}else {
[self playMusic];
}
return MPRemoteCommandHandlerStatusSuccess;
}];
/* 这部分功能没有用到,就暂时先放在这里
// 上一曲
MPRemoteCommand *previousCommand = commandCenter.previousTrackCommand;
[previousCommand addTargetWithHandler:^MPRemoteCommandHandlerStatus(MPRemoteCommandEvent * _Nonnull event) {
[self playPrevMusic];
return MPRemoteCommandHandlerStatusSuccess;
}];
// 下一曲
MPRemoteCommand *nextCommand = commandCenter.nextTrackCommand;
nextCommand.enabled = YES;
[nextCommand addTargetWithHandler:^MPRemoteCommandHandlerStatus(MPRemoteCommandEvent * _Nonnull event) {
self.isAutoPlay = NO;
if (self.isPlaying) {
[self stopMusic];
}
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[self playNextMusic];
});
return MPRemoteCommandHandlerStatusSuccess;
}];
// 快进
MPRemoteCommand *forwardCommand = commandCenter.seekForwardCommand;
forwardCommand.enabled = YES;
[forwardCommand addTargetWithHandler:^MPRemoteCommandHandlerStatus(MPRemoteCommandEvent * _Nonnull event) {
MPSeekCommandEvent *seekEvent = (MPSeekCommandEvent *)event;
if (seekEvent.type == MPSeekCommandEventTypeBeginSeeking) {
[self seekingForwardStart];
}else {
[self seekingForwardStop];
}
return MPRemoteCommandHandlerStatusSuccess;
}];
// 快退
MPRemoteCommand *backwardCommand = commandCenter.seekBackwardCommand;
backwardCommand.enabled = YES;
[backwardCommand addTargetWithHandler:^MPRemoteCommandHandlerStatus(MPRemoteCommandEvent * _Nonnull event) {
MPSeekCommandEvent *seekEvent = (MPSeekCommandEvent *)event;
if (seekEvent.type == MPSeekCommandEventTypeBeginSeeking) {
[self seekingBackwardStart];
}else {
[self seekingBackwardStop];
}
return MPRemoteCommandHandlerStatusSuccess;
}];
*/
// 拖动进度条
if (@available(iOS 9.1, *)) {
MPRemoteCommand *changePlaybackPositionCommand = commandCenter.changePlaybackPositionCommand;
changePlaybackPositionCommand.enabled = YES;
[changePlaybackPositionCommand addTargetWithHandler:^MPRemoteCommandHandlerStatus(MPRemoteCommandEvent * _Nonnull event) {
self.isDraging = YES;
NSLog(@"self.isDraging = %d",self.isDraging);
MPChangePlaybackPositionCommandEvent *positionEvent = (MPChangePlaybackPositionCommandEvent *)event;
self.positionTime = positionEvent.positionTime;
NSLog(@"positionTime = %f",self.positionTime);
//业务逻辑如下:
self.currentTime = (float)self.positionTime * 1000 >= self.courseItem.duration.integerValue * 1000 ? self.courseItem.duration.integerValue * 1000 : (float)self.positionTime * 1000;
CGFloat value = self.currentTime / self.duration;
if (self.isPlaying) {
[kAudioPlayer setPlayerProgress:value];
}else {
if (value == 0) {
value = 0.001;
}
self.seekProgress = value;
}
self.updataMediaCount = 0;
NSLog(@"self.isDraging = %d",self.isDraging);
return MPRemoteCommandHandlerStatusSuccess;
}];
} else {
// Fallback on earlier versions
}
}
使用MPNowPlayingInfoCenter更新通知中心控制台的媒体信息,当然,我们这里只是更新了我们需要的一信息
//更新通知中心控制台媒体信息
- (void)setupLockScreenMediaInfo {
MPNowPlayingInfoCenter *playingCenter = [MPNowPlayingInfoCenter defaultCenter];
NSMutableDictionary *playingInfo = [NSMutableDictionary new];
//标题
playingInfo[MPMediaItemPropertyTitle] = self.courseItem.title;
//封面图片
UIImage *coverImage = nil;
if ([NSString isEmpty:self.courseItem.cover_url]) {
coverImage = [UIImage imageNamed:@"loftyAppLogo"];
}else {
UIImageView *coverImageView = [[UIImageView alloc] init];
[coverImageView sd_setImageWithURL:[NSURL URLWithString:self.courseItem.cover_url]];
coverImage = coverImageView.image;
if (coverImage == nil) {
coverImage = [UIImage imageNamed:@"loftyAppLogo"];
}
}
MPMediaItemArtwork *artwork = [[MPMediaItemArtwork alloc] initWithImage:coverImage];
playingInfo[MPMediaItemPropertyArtwork] = artwork;
// 当前播放的时间
NSLog(@"playingInfo . currentTime = %f",self.currentTime / 1000);
playingInfo[MPNowPlayingInfoPropertyElapsedPlaybackTime] = [NSNumber numberWithFloat:self.currentTime / 1000];
// 进度的速度
playingInfo[MPNowPlayingInfoPropertyPlaybackRate] = [NSNumber numberWithFloat:1.0];
// 总时间
playingInfo[MPMediaItemPropertyPlaybackDuration] = [NSNumber numberWithFloat:self.duration / 1000];
if (@available(iOS 10.0, *)) {
playingInfo[MPNowPlayingInfoPropertyPlaybackProgress] = [NSNumber numberWithFloat:self.audioControlView.progress];
} else {
// Fallback on earlier versions
}
[playingCenter setNowPlayingInfo:playingInfo];
}
关于MPNowPlayingInfoCenter设置关键字解析的链接:关键字语意解析
#pragma mark - Notifications
- (void)addNotifications {
[[UIApplication sharedApplication] beginReceivingRemoteControlEvents];
// 插拔耳机
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(audioSessionRouteChange:) name:AVAudioSessionRouteChangeNotification object:nil];
// 播放打断,如电话打入
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(audioSessionInterruption:) name:AVAudioSessionInterruptionNotification object:nil];
}
- (void)audioSessionRouteChange:(NSNotification *)notify {
NSDictionary *interuptionDict = notify.userInfo;
NSInteger routeChangeReason = [[interuptionDict valueForKey:AVAudioSessionRouteChangeReasonKey] integerValue];
switch (routeChangeReason) {
case AVAudioSessionRouteChangeReasonNewDeviceAvailable:
NSLog(@"耳机插入");
// 继续播放音频,什么也不用做
break;
case AVAudioSessionRouteChangeReasonOldDeviceUnavailable:
{
NSLog(@"耳机拔出");
// 注意:拔出耳机时系统会自动暂停你正在播放的音频,因此只需要改变UI为暂停状态即可
if (self.isPlaying) {
[self pauseMusic];
}
}
break;
default:
break;
}
}
- (void)audioSessionInterruption:(NSNotification *)notify {
NSDictionary *interuptionDict = notify.userInfo;
NSInteger interruptionType = [[interuptionDict valueForKey:AVAudioSessionInterruptionTypeKey] integerValue];
NSInteger interruptionOption = [[interuptionDict valueForKey: AVAudioSessionInterruptionOptionKey] integerValue];
switch (interruptionType) {
case AVAudioSessionInterruptionTypeBegan:
{
// 收到播放中断的通知,暂停播放
if (self.isPlaying) {
self.isInterrupt = YES;
[self pauseMusic];
self.isPlaying = NO;
}
}
break;
case AVAudioSessionInterruptionTypeEnded:
{
if (self.isInterrupt) {
self.isInterrupt = NO;
// 中断结束,判断是否需要恢复播放
if (interruptionOption == AVAudioSessionInterruptionOptionShouldResume) {
if (!self.isPlaying) {
[self playMusic];
self.isPlaying = YES;
}
}
}
}
break;
default:
break;
}
}
当然,把这些都加进去,只是完成了音频后台播放相关的设置和代码
如果,APP内还有视频,那么申请了后台播放权限,你会发现会有很多有(头)趣(疼)的问题
特别是在音频视频切换播放时,通知中心音乐控制台的显示,是一个让我很难忘的问题!
关于这方面的问题解决,请阅读这篇博客:支持后台播放的音频、视频开发中遇到的问题