一.添加后台播放权限
- 修改Info.plist
在 Info.plist 中添加Required background modes
,并在下面添加一项 :App plays audio or streams audio/video using AirPlay
. - 修改 Capabilities
在Capabilities
中开启Background Modes
。如图所示:
- 修改 AppDelegate
在AppDelegate
的application: didFinishLaunchingWithOptions:
方法中添加以下代码:
// 告诉app支持后台播放
AVAudioSession *audioSession = [AVAudioSession sharedInstance];
[audioSession setCategory:AVAudioSessionCategoryPlayback error:nil];
[audioSession setActive:YES error:nil];
二.添加锁屏播放控制
1. 用 MPNowPlayingInfoCenter 显示歌曲信息
播放信息可以通过[MPNowPlayingInfoCenter defaultCenter]. nowPlayingInfo
进行设置,如下
- (void)updateNowPlayingInfoWithItem:(nullable WXPlayingItem *)item{
if (item == nil) {
[self updateNowPlayingInfo:nil];
return;
}
NSMutableDictionary *playInfo = [NSMutableDictionary new];
NSDictionary *curDict = [[MPNowPlayingInfoCenter defaultCenter] nowPlayingInfo];
if (curDict) {
playInfo = [curDict mutableCopy];
}
NSString * songName = item.songName.length > 0? item.songName:KString(@"未知歌曲");
NSString * singerName = item.singerName.length > 0? item.singerName:KString(@"未知歌手");
[playInfo setObject:songName forKey:MPMediaItemPropertyTitle]; //歌曲
[playInfo setObject:singerName forKey:MPMediaItemPropertyArtist]; //歌手
if (item.albumTitle != nil) { //专辑
[playInfo setObject:item.albumTitle forKey:MPMediaItemPropertyAlbumTitle];
}
[playInfo setObject:[NSNumber numberWithDouble:item.curTime] forKey:MPNowPlayingInfoPropertyElapsedPlaybackTime]; //已播放时长
[playInfo setObject:[NSNumber numberWithDouble:item.totalTime] forKey:MPMediaItemPropertyPlaybackDuration]; //歌曲时长
KLog(@"[WXPlayer] UpdateInfo:songName = %@,singerName = %@,albumTitle = %@,playbackTime = %d,playbackDuration = %d",songName,singerName,item.albumTitle,(int)item.curTime,(int)(item.totalTime - item.curTime));
//设置封面
if (item.albumImg != nil) {
MPMediaItemArtwork *artwork = [[MPMediaItemArtwork alloc] initWithImage:item.albumImg];
if (artwork) {
[playInfo setObject:artwork forKey:MPMediaItemPropertyArtwork];
}
}
[self updateNowPlayingInfo:playInfo];
}
- (void)updateNowPlayingInfo:(NSDictionary *)playInfo{
KLog(@"[WXPlayer] infoDict=%@", playInfo);
[[MPNowPlayingInfoCenter defaultCenter] setNowPlayingInfo:playInfo];
[self startHandler];
MPRemoteCommandCenter * commandCenter = [MPRemoteCommandCenter sharedCommandCenter];
commandCenter.previousTrackCommand.enabled = self.curPlayer.onRemoteCommand_hasPre;
commandCenter.nextTrackCommand.enabled = self.curPlayer.onRemoteCommand_hasNext;
}
显示效果如下
2. 用 MPRemoteCommandCenter
实现播放控制
在 iOS 7.1 之后,可以通过 MPRemoteCommandCenter
来控制音频播放。每个控制操作都封装为一个 MPRemoteCommand
对象,给 MPRemoteCommand
添加响应事件有两种方式:
-
addTargetWithHandler:
以 Block 的方式传入响应事件,需要返回MPRemoteCommandHandlerStatusSuccess
来告知响应成功。 -
addTarget: action:
因为MPRemoteCommandCenter
是个单例,所以在 target 的 dealloc 中要记得调用 removeTarget: 。如下所示:
- (void)removeCommandCenterTargets {
MPRemoteCommandCenter *commandCenter = [MPRemoteCommandCenter sharedCommandCenter];
[commandCenter.playCommand removeTarget:self];
[commandCenter.pauseCommand removeTarget:self];
[commandCenter.togglePlayPauseCommand removeTarget:self];
[commandCenter.nextTrackCommand removeTarget:self];
[commandCenter.previousTrackCommand removeTarget:self];
if (@available(iOS 9.1, *)) {
[commandCenter.changePlaybackPositionCommand removeTarget:self];
}
}
注意:因为
changePlaybackPositionCommand
在 iOS 9.1 以后才可用,所以这里加了系统判断。
以下是监听MPRemoteCommandCenter
事件的逻辑
-(void) startHandler{
if (!self.isInitHandler) {
//返回值根据需要返回,一般情况下返回MPRemoteCommandHandlerStatusSuccess即可
MPRemoteCommandCenter * commandCenter = [MPRemoteCommandCenter sharedCommandCenter];
KS_WEAK_SELF(self);
commandCenter.playCommand.enabled = YES;
[commandCenter.playCommand addTargetWithHandler:^MPRemoteCommandHandlerStatus(MPRemoteCommandEvent * _Nonnull event) {
CHECK_SELF_AND_RETURN_WITH(MPRemoteCommandHandlerStatusCommandFailed);
KLog(@"[WXPlayer] Play");
[self.curPlayer onRemoteCommand_play];
return MPRemoteCommandHandlerStatusSuccess;
}];
commandCenter.pauseCommand.enabled = YES;
[commandCenter.pauseCommand addTargetWithHandler:^MPRemoteCommandHandlerStatus(MPRemoteCommandEvent * _Nonnull event) {
CHECK_SELF_AND_RETURN_WITH(MPRemoteCommandHandlerStatusCommandFailed);
KLog(@"[WXPlayer] Pause");
[self.curPlayer onRemoteCommand_pause];
return MPRemoteCommandHandlerStatusSuccess;
}];
commandCenter.togglePlayPauseCommand.enabled = YES;
[commandCenter.togglePlayPauseCommand addTargetWithHandler:^MPRemoteCommandHandlerStatus(MPRemoteCommandEvent * _Nonnull event) {
CHECK_SELF_AND_RETURN_WITH(MPRemoteCommandHandlerStatusCommandFailed);
KLog(@"[WXPlayer] Play Or Pause");
[self.curPlayer onRemoteCommand_playOrPause];
return MPRemoteCommandHandlerStatusSuccess;
}];
[commandCenter.previousTrackCommand addTargetWithHandler:^MPRemoteCommandHandlerStatus(MPRemoteCommandEvent * _Nonnull event) {
CHECK_SELF_AND_RETURN_WITH(MPRemoteCommandHandlerStatusCommandFailed);
KLog(@"[WXPlayer] PlayPre");
[self.curPlayer onRemoteCommand_playPre];
return MPRemoteCommandHandlerStatusSuccess;
}];
[commandCenter.nextTrackCommand addTargetWithHandler:^MPRemoteCommandHandlerStatus(MPRemoteCommandEvent * _Nonnull event) {
CHECK_SELF_AND_RETURN_WITH(MPRemoteCommandHandlerStatusCommandFailed);
KLog(@"[WXPlayer] PlayNext")
[self.curPlayer onRemoteCommand_playNext];
return MPRemoteCommandHandlerStatusSuccess;
}];
//9.1以上版本支持
if(IS_OS_91_OR_LATER) {
commandCenter.changePlaybackPositionCommand.enabled = YES;
[commandCenter.changePlaybackPositionCommand addTargetWithHandler:^MPRemoteCommandHandlerStatus(MPRemoteCommandEvent * _Nonnull event) {
CHECK_SELF_AND_RETURN_WITH(MPRemoteCommandHandlerStatusCommandFailed);
MPChangePlaybackPositionCommandEvent *positionCommandEvent = SAFE_CAST(event, MPChangePlaybackPositionCommandEvent);
if (positionCommandEvent == nil) {
KLog(@"[WXPlayer] Seek Error")
return MPRemoteCommandHandlerStatusCommandFailed;
}
NSTimeInterval positionTime = positionCommandEvent.positionTime;
KLog(@"[WXPlayer] Seek to time=%f", positionTime);
[self.curPlayer onRemoteCommand_seekTo:positionTime];
return MPRemoteCommandHandlerStatusSuccess;
}];
}
self.isInitHandler = YES;
}
}
补上其它代码:
WXPlayerRemoteControlMgr.h
#import
#import "WXPlayingItem.h"
NS_ASSUME_NONNULL_BEGIN
@protocol WXRemoteCommandProtocol
-(BOOL) onRemoteCommand_hasPre;
-(BOOL) onRemoteCommand_hasNext;
-(void) onRemoteCommand_playPre;
-(void) onRemoteCommand_playNext;
-(void) onRemoteCommand_play;
-(void) onRemoteCommand_pause;
-(void) onRemoteCommand_seekTo:(NSTimeInterval) positionTime;
@optional
-(void) onRemoteCommand_playOrPause;
@end
@interface WXPlayerRemoteControlMgr : NSObject
DECL_SINGLETON(WXPlayerRemoteControlMgr);
@property(nonatomic,strong,nullable) id curPlayer;
/*
更新时机:
1.播放器状态变成playing
2.切歌
3.seek完之后
4.暂停/开始播放
3.封面图下载完成
清空时机:需要stop或interrupt的时候
*/
- (void)updateNowPlayingInfoWithItem:(nullable WXPlayingItem *)item;
@end
NS_ASSUME_NONNULL_END
WXPlayerRemoteControlMgr.m
#import "WXPlayerRemoteControlMgr.h"
#import
@interface WXPlayerRemoteControlMgr ()
@property (nonatomic,strong) NSMutableArray * targetList;
@property(nonatomic,assign) BOOL isInitHandler;
@end
@implementation WXPlayerRemoteControlMgr
IMPL_SINGLETON(WXPlayerRemoteControlMgr);
- (instancetype)init {
self = [super init];
if (self) {
self.targetList = NSMutableArray.new;
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(applicationBecomeActive:)
name:UIApplicationDidBecomeActiveNotification object:nil];
}
return self;
}
-(void) start{
if (self.curPlayer != nil) {
[[UIApplication sharedApplication] endReceivingRemoteControlEvents];
[[UIApplication sharedApplication] beginReceivingRemoteControlEvents];
}
}
-(void) stop{
[[UIApplication sharedApplication] endReceivingRemoteControlEvents];
}
- (void)applicationBecomeActive:(NSNotification*)notification {
[self start];
}
- (void)dealloc {
[self stop];
}
- (void)removeCommandCenterTargets {
MPRemoteCommandCenter *commandCenter = [MPRemoteCommandCenter sharedCommandCenter];
[commandCenter.playCommand removeTarget:self];
[commandCenter.pauseCommand removeTarget:self];
[commandCenter.togglePlayPauseCommand removeTarget:self];
[commandCenter.nextTrackCommand removeTarget:self];
[commandCenter.previousTrackCommand removeTarget:self];
if (@available(iOS 9.1, *)) {
[commandCenter.changePlaybackPositionCommand removeTarget:self];
}
}
-(void) startHandler{
if (!self.isInitHandler) {
//返回值根据需要返回,一般情况下返回MPRemoteCommandHandlerStatusSuccess即可
MPRemoteCommandCenter * commandCenter = [MPRemoteCommandCenter sharedCommandCenter];
KS_WEAK_SELF(self);
commandCenter.playCommand.enabled = YES;
[commandCenter.playCommand addTargetWithHandler:^MPRemoteCommandHandlerStatus(MPRemoteCommandEvent * _Nonnull event) {
CHECK_SELF_AND_RETURN_WITH(MPRemoteCommandHandlerStatusCommandFailed);
KLog(@"[WXPlayer] Play");
[self.curPlayer onRemoteCommand_play];
return MPRemoteCommandHandlerStatusSuccess;
}];
commandCenter.pauseCommand.enabled = YES;
[commandCenter.pauseCommand addTargetWithHandler:^MPRemoteCommandHandlerStatus(MPRemoteCommandEvent * _Nonnull event) {
CHECK_SELF_AND_RETURN_WITH(MPRemoteCommandHandlerStatusCommandFailed);
KLog(@"[WXPlayer] Pause");
[self.curPlayer onRemoteCommand_pause];
return MPRemoteCommandHandlerStatusSuccess;
}];
commandCenter.togglePlayPauseCommand.enabled = YES;
[commandCenter.togglePlayPauseCommand addTargetWithHandler:^MPRemoteCommandHandlerStatus(MPRemoteCommandEvent * _Nonnull event) {
CHECK_SELF_AND_RETURN_WITH(MPRemoteCommandHandlerStatusCommandFailed);
KLog(@"[WXPlayer] Play Or Pause");
[self.curPlayer onRemoteCommand_playOrPause];
return MPRemoteCommandHandlerStatusSuccess;
}];
[commandCenter.previousTrackCommand addTargetWithHandler:^MPRemoteCommandHandlerStatus(MPRemoteCommandEvent * _Nonnull event) {
CHECK_SELF_AND_RETURN_WITH(MPRemoteCommandHandlerStatusCommandFailed);
KLog(@"[WXPlayer] PlayPre");
[self.curPlayer onRemoteCommand_playPre];
return MPRemoteCommandHandlerStatusSuccess;
}];
[commandCenter.nextTrackCommand addTargetWithHandler:^MPRemoteCommandHandlerStatus(MPRemoteCommandEvent * _Nonnull event) {
CHECK_SELF_AND_RETURN_WITH(MPRemoteCommandHandlerStatusCommandFailed);
KLog(@"[WXPlayer] PlayNext")
[self.curPlayer onRemoteCommand_playNext];
return MPRemoteCommandHandlerStatusSuccess;
}];
//9.1以上版本支持
if(IS_OS_91_OR_LATER) {
commandCenter.changePlaybackPositionCommand.enabled = YES;
[commandCenter.changePlaybackPositionCommand addTargetWithHandler:^MPRemoteCommandHandlerStatus(MPRemoteCommandEvent * _Nonnull event) {
CHECK_SELF_AND_RETURN_WITH(MPRemoteCommandHandlerStatusCommandFailed);
MPChangePlaybackPositionCommandEvent *positionCommandEvent = SAFE_CAST(event, MPChangePlaybackPositionCommandEvent);
if (positionCommandEvent == nil) {
KLog(@"[WXPlayer] Seek Error")
return MPRemoteCommandHandlerStatusCommandFailed;
}
NSTimeInterval positionTime = positionCommandEvent.positionTime;
KLog(@"[WXPlayer] Seek to time=%f", positionTime);
[self.curPlayer onRemoteCommand_seekTo:positionTime];
return MPRemoteCommandHandlerStatusSuccess;
}];
}
self.isInitHandler = YES;
}
}
- (void)updateNowPlayingInfoWithItem:(nullable WXPlayingItem *)item{
if (item == nil) {
[self updateNowPlayingInfo:nil];
return;
}
NSMutableDictionary *playInfo = [NSMutableDictionary new];
NSDictionary *curDict = [[MPNowPlayingInfoCenter defaultCenter] nowPlayingInfo];
if (curDict) {
playInfo = [curDict mutableCopy];
}
NSString * songName = item.songName.length > 0? item.songName:KString(@"未知歌曲");
NSString * singerName = item.singerName.length > 0? item.singerName:KString(@"未知歌手");
[playInfo setObject:songName forKey:MPMediaItemPropertyTitle]; //歌曲
[playInfo setObject:singerName forKey:MPMediaItemPropertyArtist]; //歌手
if (item.albumTitle != nil) { //专辑
[playInfo setObject:item.albumTitle forKey:MPMediaItemPropertyAlbumTitle];
}
[playInfo setObject:[NSNumber numberWithDouble:item.curTime] forKey:MPNowPlayingInfoPropertyElapsedPlaybackTime]; //已播放时长
[playInfo setObject:[NSNumber numberWithDouble:item.totalTime] forKey:MPMediaItemPropertyPlaybackDuration]; //歌曲时长
KLog(@"[WXPlayer] UpdateInfo:songName = %@,singerName = %@,albumTitle = %@,playbackTime = %d,playbackDuration = %d",songName,singerName,item.albumTitle,(int)item.curTime,(int)(item.totalTime - item.curTime));
//设置封面
if (item.albumImg != nil) {
MPMediaItemArtwork *artwork = [[MPMediaItemArtwork alloc] initWithImage:item.albumImg];
if (artwork) {
[playInfo setObject:artwork forKey:MPMediaItemPropertyArtwork];
}
}
[self updateNowPlayingInfo:playInfo];
}
- (void)updateNowPlayingInfo:(NSDictionary *)playInfo{
KLog(@"[WXPlayer] infoDict=%@", playInfo);
[[MPNowPlayingInfoCenter defaultCenter] setNowPlayingInfo:playInfo];
[self startHandler];
MPRemoteCommandCenter * commandCenter = [MPRemoteCommandCenter sharedCommandCenter];
commandCenter.previousTrackCommand.enabled = self.curPlayer.onRemoteCommand_hasPre;
commandCenter.nextTrackCommand.enabled = self.curPlayer.onRemoteCommand_hasNext;
}
@end