iOS音乐后台播放锁屏封面及播放控制

Posted 想名真难

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了iOS音乐后台播放锁屏封面及播放控制相关的知识,希望对你有一定的参考价值。

在默认环境下App被切换到后台时,音乐的就停止播放了,但音乐类App的一般都会需要在后台继续播放,这样用户就可以一边听音乐,一边操作其他的App。对于这种情况我们可以对App做一些简单的配置,实现后台播放功能。当app切换到后台,用户就无法控制和查看app当前播放歌曲了。这个对于用户来说并不是很友好。既然是后台播放,那么就应该提供便捷的播放控制方式。ios系统已经预留了接口,允许开发者在锁屏界面显示播放歌曲信息(以下称为锁屏封面),以及在底部菜单栏提供播放控制器。下面我们就来给App添加这些功能吧。

一、后台播放

然后再程序中添加入下代码:

AVAudioSession  *session  =  [AVAudioSession  sharedInstance];
[session setActive:YES error:nil];
[session setCategory:AVAudioSessionCategoryPlayback error:nil];

二、添加播放控制器(Remote Control Events)

首先我们要告诉系统,我要接受系统的播放控制消息,这样系统才会给我们发送播放控制命令。流程是这样的:

App启动 -> 告诉系统我需要接受播放控制消息 -> 等待 -> 用户点击系统播放控制器按钮 -> 系统传递消息给App -> 我们接受到消息,做出相应的响应。

想要接收播放控制消息,我们必须要做三件事:

  • 成为Frist Responder
  • 请求系统,要求开始监听播放控制消息(Remote Control Events)
  • 开始播放音频。

请注意第三点,我们的App必须在开始播放音频后,才能收到控制消息。否则,即使你满足了前两点,也无法接收到控制消息。

//AppDelegate.m
-  (BOOL)application:(UIApplication  *)application didFinishLaunchingWithOptions:(NSDictionary  *)launchOptions 
//告诉系统,我们要接受远程控制事件
   [[UIApplication  sharedApplication] beginReceivingRemoteControlEvents];
   [self  becomeFirstResponder];


-  (BOOL)canBecomeFirstResponder 
   return  YES;


//响应远程音乐播放控制消息
- (void)ul_remoteControlReceivedWithEvent:(UIEvent *)event 
    if(event.type == UIEventTypeRemoteControl) // 判断是否远程控制
        switch (event.subtype) 
            case UIEventSubtypeRemoteControlPlay:// 播放
                [self playActionDealWith];
                break;
            case UIEventSubtypeRemoteControlPause:// 暂停
                [self pauseActionDealWith];
                break;
            case UIEventSubtypeRemoteControlNextTrack:// 下一首
                [self nextTrackActionDealWithAutoEnd:NO];
                [self resetCloseUntilCurrentDone];
                break;
            case UIEventSubtypeRemoteControlPreviousTrack:// 上一首
                [self previousTrackActionDealWith];
                [self resetCloseUntilCurrentDone];
                break;
            case UIEventSubtypeRemoteControlTogglePlayPause:
                if ([ULRadioDramaPlayerManager shareInstance].isPlaying) 
                    [self pauseActionDealWith];
                else
                    [self playActionDealWith];
                
                break;
            default:
                break;
        
        ULLogInfo(@"锁屏状态操作类型:%ld",(long)event.subtype);
    

播放音频的代码,这里给出一段简单的示例:

-  (void)playBtnClicked
    NSError  *error  =  nil;
    NSString  *path  =  [[NSBundle  mainBundle] pathForResource:@"music" ofType:@"mp3"];
    AVAudioPlayer  *player  =  [[AVAudioPlayer  alloc] initWithContentsOfURL:[NSURL URLWithString:path] error:&error];

    if  (error)  
        NSLog(@"Error:%@",  [error localizedDescription]);
    
    [player play];

在开始播放音频后,使用耳机线控的播放暂停等按键,或者锁屏封面上的播放控制按键,就能够收到控制消息了。

关于耳机线控的一点说明

苹果耳机的线控上有三个按钮:加号,中部,减号。其中加号和减号是用于控制音量,这两个按钮点击是收不到消息的——UIEventSubtype没有音量改变的事件类型。而中部按钮的点击,是可以收到消息的,按一下是播放/暂停切换,快按两下是播放下一首,快按三下是播放上一首,快按两下并摁住是快进,快按三下并摁住是快退。

三、在锁屏界面显示播放歌曲信息

代码如下,其实就是设置一个全局变量的值,当系统处于音乐播放状态时,锁屏界面就会将NowPlayingInfo中的信息展示出来。可惜的是,这里的定制性不是太强,例如歌曲图片无法平铺整个屏幕大小,根据我的测试,歌曲图片在320×320时,可以完整显示在屏幕中央位置,两侧不会留下黑边。

#import <MediaPlayer/MediaPlayer.h>


- (void)updatePlayInfo 

    NSMutableDictionary *songInfo = [[NSMutableDictionary alloc] init];
    UIImage *image = [UIImage imageNamed:@"image"];
    MPMediaItemArtwork *albumArt = [[MPMediaItemArtwork alloc] initWithImage:image];
    //歌曲名称
    [songInfo setObject:@"深夜地下铁" forKey:MPMediaItemPropertyTitle];
    //演唱者
    [songInfo setObject:@"陶钰玉" forKey:MPMediaItemPropertyArtist];
    //专辑名
    [songInfo setObject:@"深夜地下铁" forKey:MPMediaItemPropertyAlbumTitle];
    //专辑缩略图
    [songInfo setObject:albumArt forKey:MPMediaItemPropertyArtwork];
    [songInfo setObject:[NSNumber numberWithDouble:10.0] forKey:MPNowPlayingInfoPropertyElapsedPlaybackTime]; //音乐当前已经播放时间
    [songInfo setObject:[NSNumber numberWithFloat:1.0] forKey:MPNowPlayingInfoPropertyPlaybackRate];//进度光标的速度 (这个随 自己的播放速率调整,我默认是原速播放)
    [songInfo setObject:[NSNumber numberWithDouble:30.0] forKey:MPMediaItemPropertyPlaybackDuration];//歌曲总时间设置

    //        设置锁屏状态下屏幕显示音乐信息
    [[MPNowPlayingInfoCenter defaultCenter] setNowPlayingInfo:songInfo];


经过了如上配置后,程序应该就能够正常显示了。

简单demo地址 :  音频后台播放、锁屏封面及播放控制


发现一个系统特性, 正常启动app并开始播放, 这时候会把锁屏信息设置上, 然后在退后台, 把app杀死, 此时锁屏信息被系统清理掉, 目前都是正常且符合预期的,

但是通过系统工具栏点击播放, 此时系统会默默的启动上一次设置播放信息的app, 并调用remoteControlReceivedWithEvent:尝试再次播放音频,如果app记录了上次播放的音频地址,那就可以做到再次播放,当然player也需要重新初始化,并且再次点击app, 发现这次启动是一次热启动, app已经在首页位置了.

但是一般用户看到工具栏上什么信息都没有也是不会点的,点击了也不会有预期上次的音频继续播放,所以这个就作为一个iOS的小特性吧.


还发现一个系统bug, 复现步骤是

  1. 启动app,
  2. 开始播放10s,
  3. 然后暂停,
  4. 退后台,
  5. 等待若干秒,
    1.如果没有做后台保活-beginBackgroundTaskWithExpirationHandler,很快会被挂起; 
    2.如果做了保活,就需要等待后台保活的超时了,可能是180s,可能是30s, 不同系统这个值不一样
  6. 当app的状态是被挂起时, 在通过工具栏点击播放, 会出现播放很短音频, 1s左右, 然后就被暂停, 再次点击播放按钮才能恢复播放. 也就是app挂起状态下, 点击2次才能正常播放.

正常的期望应该是点击一次就可以播放了, 而且也尝试了在没有挂起的情况下,确实是点击一次就可以完成播放. 那为什么挂起状态下需要点击2次呢?

经过排查, 发现系统在第一次点击播放时,显示了此行log. 这是app被挂起时,AVPlayer发出的通知

AVAudioSession.mm:2285:-[AVAudioSession privateInterruptionWithInfo:]: Posting AVAudioSessionInterruptionNotification (Begin Interruption). Was suspended:1 

那我也监听  AVAudioSessionInterruptionNotification 通知,看看内容是什么, 打印通知为

-interruptionAction NSConcreteNotification 0x28164bdb0 name = AVAudioSessionInterruptionNotification; object = <AVAudioSession: 0x281a14ba0>; userInfo = 
    AVAudioSessionInterruptionTypeKey = 1;
    AVAudioSessionInterruptionWasSuspendedKey = 1;
 

 由此确认, 的确会有一个播放被打断的通知过来.

猜测可能是app被挂起了, 系统准备发送此通知, 但是由于系统没有分配CPU时间, 这个通知并没有发送成功.
当第一次点击播放时, 系统给app分配了CPU时间, 播放器开始播放,  同时上次被挂起的通知也发送成功, AVPlayer接受到此通知, 立即调用了暂停, 所以出现了播放很短时间就被暂停的现象,
第二次点击, 因为app已经不是被挂起的状态了, 点击播放就可以正常播放了.

怎么解决呢? 我的做法是监听通知, 然后确认如果是因为被挂起的打断, 就继续播放.

- (void)interruptionAction:(NSNotification *)noti 
    NSLog(@"-interruptionAction %@",noti);
    if ([noti.userInfo[AVAudioSessionInterruptionWasSuspendedKey] boolValue] == YES) 
        NSLog(@"因为被挂起而打断");
        if (self.player.rate == 0) 
            [self.player play];
            _isPlayingNow = YES;
        
    

以上是关于iOS音乐后台播放锁屏封面及播放控制的主要内容,如果未能解决你的问题,请参考以下文章

iOS音乐后台播放及锁屏信息显示

iOS音频的后台播放总结(后台网络请求歌曲,Remote控制,锁屏封面,各种打断)

ios16音乐播放器变小了

AVPlayer 音乐播放后台播放,以及锁屏主题设置

IOS后台运行 之 后台播放音乐

【iOS】后台播报TTS(防止APP后台被杀死)