最近在做音乐播放器,要静音状态下播放,后台播放,远程控制,锁屏显示,上拉菜单控制,来电中断处理等。
开启后台模式:
在TARGETS-Capabilities-Background Modes 勾选Audio,AirPay and Picture in Picture
在AppDelegate中
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
//静音状态下播放
[[AVAudioSession sharedInstance] setActive:YES error:nil];
//处理电话打进时中断音乐播放
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(interruptionNotificationHandler:) name:AVAudioSessionInterruptionNotification object:nil];
//后台播放
[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback error:nil];
// 在App启动后开启远程控制事件, 接收来自锁屏界面和上拉菜单的控制
[application beginReceivingRemoteControlEvents];
// 处理远程控制事件
[self remoteControlEventHandler];
self.window= [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
[self.window makeKeyAndVisible];
MusicListTableVC * vc = [[MusicListTableViewController alloc] init];
UINavigationController * nc = [[UINavigationController alloc] initWithRootViewController:vc];
self.window.rootViewController = nc;
return YES;
}
来电中断处理:
//来电中断处理
- (void)interruptionNotificationHandler:(NSNotification*)notification
{
NSDictionary *interuptionDict = notification.userInfo;
NSString *type = [NSString stringWithFormat:@"%@", [interuptionDict valueForKey:AVAudioSessionInterruptionTypeKey]];
NSUInteger interuptionType = [type integerValue];
if (interuptionType == AVAudioSessionInterruptionTypeBegan) {
//获取中断前音乐是否在播放
_played = [MusicPlayViewController shareMusicPlay].isPlaying;
NSLog(@"AVAudioSessionInterruptionTypeBegan");
}else if (interuptionType == AVAudioSessionInterruptionTypeEnded) {
NSLog(@"AVAudioSessionInterruptionTypeEnded");
}
if(_played)
{
//停止播放的事件
[[MusicPlayTools shareMusicPlay] musicPause];
_played=NO;
}else {
//继续播放的事件
[[MusicPlayTools shareMusicPlay] musicPlay];
_played=YES;
}
}
- (void)applicationWillResignActive:(UIApplication *)application
{
NSLog(@"要挂起了。。。");
//更新锁屏信息
[[MusicPlayViewController shareMusicPlay] configNowPlayingInfoCenter];
}
- (void)applicationWillTerminate:(UIApplication *)application
{
// 在App要终止前结束接收远程控制事件, 也可以在需要终止时调用该方法终止
[application endReceivingRemoteControlEvents];
}
远程控制事件处理:
// 在需要处理远程控制事件的具体控制器或其它类中实现
- (void)remoteControlEventHandler
{
// 直接使用sharedCommandCenter来获取MPRemoteCommandCenter的shared实例
MPRemoteCommandCenter *commandCenter = [MPRemoteCommandCenter sharedCommandCenter];
// 启用播放命令 (锁屏界面和上拉快捷功能菜单处的播放按钮触发的命令)
commandCenter.playCommand.enabled = YES;
// 为播放命令添加响应事件, 在点击后触发
[commandCenter.playCommand addTargetWithHandler:^MPRemoteCommandHandlerStatus(MPRemoteCommandEvent * _Nonnull event) {
[[MusicPlayTools shareMusicPlay] musicPlay];
[[MusicPlayViewController shareMusicPlay] configNowPlayingInfoCenter];
return MPRemoteCommandHandlerStatusSuccess;
}];
// 播放, 暂停, 上下曲的命令默认都是启用状态, 即enabled默认为YES
[commandCenter.pauseCommand addTargetWithHandler:^MPRemoteCommandHandlerStatus(MPRemoteCommandEvent * _Nonnull event) {
//点击了暂停
[[MusicPlayTools shareMusicPlay] musicPause];
[[MusicPlayViewController shareMusicPlay] configNowPlayingInfoCenter];
return MPRemoteCommandHandlerStatusSuccess;
}];
[commandCenter.previousTrackCommand addTargetWithHandler:^MPRemoteCommandHandlerStatus(MPRemoteCommandEvent * _Nonnull event) {
//点击了上一首
[[MusicPlayViewController shareMusicPlay] lastSongAction];
[[MusicPlayViewController shareMusicPlay] configNowPlayingInfoCenter];
return MPRemoteCommandHandlerStatusSuccess;
}];
[commandCenter.nextTrackCommand addTargetWithHandler:^MPRemoteCommandHandlerStatus(MPRemoteCommandEvent * _Nonnull event) {
//点击了下一首
[[MusicPlayViewController shareMusicPlay] nextSongButtonAction:nil];
[[MusicPlayViewController shareMusicPlay] configNowPlayingInfoCenter];
return MPRemoteCommandHandlerStatusSuccess;
}];
// 启用耳机的播放/暂停命令 (耳机上的播放按钮触发的命令)
commandCenter.togglePlayPauseCommand.enabled = YES;
// 为耳机的按钮操作添加相关的响应事件
[commandCenter.togglePlayPauseCommand addTargetWithHandler:^MPRemoteCommandHandlerStatus(MPRemoteCommandEvent * _Nonnull event) {
// 进行播放/暂停的相关操作 (耳机的播放/暂停按钮)
[[MusicPlayViewController shareMusicPlay] playPauseButtonAction:nil];
[[MusicPlayViewController shareMusicPlay] configNowPlayingInfoCenter];
return MPRemoteCommandHandlerStatusSuccess;
}];
}
锁频界面上所显示的歌曲播放信息和图片,控制中心上显示的歌曲播放信息等,这些信息的显示都由MPNowPlayingInfoCenter类来控制。
首先#import <MediaPlayer/MPNowPlayingInfoCenter.h>然后调用MPNowPlayingInfoCenter的单例方法获取实例,再把需要显示的信息组织成Dictionary并赋值给nowPlayingInfo属性就完成了。
其中常用的是MPNowPlayingInfoPropertyElapsedPlaybackTime和MPNowPlayingInfoPropertyPlaybackRate:
- MPNowPlayingInfoPropertyElapsedPlaybackTime表示已经播放的时间,用这个属性可以让NowPlayingCenter显示播放进度;
- MPNowPlayingInfoPropertyPlaybackRate表示播放速率。通常情况下播放速率为1.0,即真是时间的1秒对应播放时间中的1秒;
这里需要解释的是,NowPlayingCenter中的进度刷新并不是由app不停的更新nowPlayingInfo来做的,而是根据app传入的ElapsedPlaybackTime和PlaybackRate进行自动刷新。例如传入ElapsedPlaybackTime=120s,PlaybackRate=1.0,那么NowPlayingCenter会显示2:00并且在接下来的时间中每一秒把进度加1秒并刷新显示。如果需要暂停进度,传入PlaybackRate=0.0即可。
所以每次播放暂停和继续都需要更新NowPlayingCenter并正确设置ElapsedPlaybackTime和PlaybackRate否则NowPlayingCenter中的播放进度无法正常显示。
NowPlayingCenter的刷新时机
频繁的刷新NowPlayingCenter并不可取,特别是在有Artwork的情况下。所以需要在合适的时候进行刷新。
依照我自己的经验下面几个情况下刷新NowPlayingCenter比较合适:
- 当前播放歌曲进度被拖动时
- 当前播放的歌曲变化时
- 播放暂停或者恢复时
- 当前播放歌曲的信息发生变化时(例如Artwork,duration等)
在刷新时可以适当的通过判断app是否active来决定是否必须刷新以减少刷新次数。
/**
* 设置锁屏信息
*/
-(void)configNowPlayingInfoCenter
{
Class playingInfoCenter = NSClassFromString(@"MPNowPlayingInfoCenter");
if (playingInfoCenter) {
MusicInfoModel * model = [MusicPlayTools shareMusicPlay].model;
NSMutableDictionary *songInfo = [[NSMutableDictionary alloc] init];
UIImage *image = [UIImage imageNamed:[NSString stringWithFormat:@"%ld", self.index + 1]];
MPMediaItemArtwork *albumArt = [[MPMediaItemArtwork alloc] initWithBoundsSize:image.size requestHandler:^UIImage * _Nonnull(CGSize size) {
return image;
}];
//歌曲名称
[songInfo setObject:model.name forKey:MPMediaItemPropertyTitle];
//演唱者
[songInfo setObject:model.singer forKey:MPMediaItemPropertyArtist];
//专辑名
[songInfo setObject:@"专辑名" forKey:MPMediaItemPropertyAlbumTitle];
//专辑缩略图
[songInfo setObject:albumArt forKey:MPMediaItemPropertyArtwork];
//音乐当前已经播放时间
NSInteger currentTime = [[MusicPlayTools shareMusicPlay] getCurTime];
[songInfo setObject:[NSNumber numberWithInteger:currentTime] forKey:MPNowPlayingInfoPropertyElapsedPlaybackTime];
//进度光标的速度 (这个随 自己的播放速率调整,我默认是原速播放)
[songInfo setObject:[NSNumber numberWithFloat:1.0] forKey:MPNowPlayingInfoPropertyPlaybackRate];
//歌曲总时间设置
NSInteger duration = [model.duration integerValue];
[songInfo setObject:[NSNumber numberWithInteger:duration] forKey:MPMediaItemPropertyPlaybackDuration];
//设置锁屏状态下屏幕显示音乐信息
[[MPNowPlayingInfoCenter defaultCenter] setNowPlayingInfo:songInfo];
}
}