iOS 音频后台播放 && 锁屏显示及控制

2023-10-16 09:53:23 浏览数 (2)

播放锁屏通知栏显示

背景

播放音频时,希望通知界面能显示,且能控制音频播放。由于之前需求是进入后台时播放暂停,所以每次打开通知界面时,播放就暂停,看不到类似于音乐播放器那样的效果。后来发现,去除进入后台暂停代码后,通知界面就可以显示播放器,但是不能控制、且没有进度。

实现

支持后台播放

首先需要 APP 支持后台播放,即,一方面去除进入后台播放暂停的代码逻辑;另一方面,设置 Target -> Signing & Capabilities 中,添加 Backgroud Modes,打开 Audio, AirPlay, and Picture in Picture。图片如下:

注意设置AVAudioSession,播放前根据实际需要设置,播放后关闭

AVAudioSessionCategory类型

Category类型

当按”静音”或者锁屏时是否静音

是否可以和其他支持混音的 APP 混合播放

是否支持后台

场景举例描述

AVAudioSessionCategoryAmbient

常用于 APP 的背景音,比如玩游戏时还可以听音乐

AVAudioSessionCategorySoloAmbient

同样是背景音,但是用于玩游戏时不想听音乐的场景

AVAudioSessionCategoryPlayback

默认不可以,但可支持

音乐播放,锁屏时还能听音乐

AVAudioSessionCategoryRecord

否,只能录音

录音机,录音时,其他音乐不能播放

AVAudioSessionCategoryPlayAndRecord

默认可以,即可以录音也可以播放

边播边录,比如 VOIP 这样的场景

AVAudioSessionCategoryAudioProcessing

否,硬件解码音频,不能播放和录制

用于音频格式处理

AVAudioSessionCategoryMultiRoute

是,多种输入输出

耳机、USB 设备同时播放

AVAudioSessionCategoryOption类型

CategoryOption类型

描述

适用类别

AVAudioSessionCategoryOptionMixWithOthers

支持和其他APP 混合播放

AVAudioSessionCategoryPlayAndRecord、AVAudioSessionCategoryPlayback、AVAudioSessionCategoryMultiRoute

AVAudioSessionCategoryOptionDuckOthers

调低其他 APP 音频音量,突出本 APP 的音量

AVAudioSessionCategoryPlayAndRecord、AVAudioSessionCategoryPlayback、AVAudioSessionCategoryMultiRoute

AVAudioSessionCategoryOptionAllowBluetooth

支持蓝牙音频输入

AVAudioSessionCategoryRecord、AVAudioSessionCategoryPlayAndRecord

AVAudioSessionCategoryOptionDefaultToSpeaker

设置默认输出音频到扬声器

AVAudioSessionCategoryPlayAndRecord

AVAudioSessionCategoryOptionInterruptSpokenAudioAndMixWithOthers

App 偶尔有用到音频播放,且播放时停止其他应用音频

AVAudioSessionCategoryPlayback、AVAudioSessionCategoryPlayAndRecord、AVAudioSessionCategoryMultiRoute

AVAudioSessionCategoryOptionAllowBluetoothA2DP

支持立体声蓝牙

AVAudioSessionCategoryPlayAndRecord

AVAudioSessionCategoryOptionAllowAirPlay

支持 AirPlay 设备

AVAudioSessionCategoryPlayAndRecord

代码语言:javascript复制

func setupAudioSession() {
    do {
        // 设置.notifyOthersOnDeactivation,当 Active 为 false 是生效,通知系统本应用播放已结束,可继续其他 APP 播放
        try AVAudioSession.sharedInstance().setActive(true, options: AVAudioSession.SetActiveOptions.notifyOthersOnDeactivation)
        
        // 根据实际需要切换设置不同的 Category
        try AVAudioSession.sharedInstance().setCategory(AVAudioSession.Category.playback, options: AVAudioSession.CategoryOptions.duckOthers)
    } catch {
        print("set AudioSession error: %@", error)
    }
}

锁屏通知栏显示

APP 支持后台播放后,可以看到在通知栏已经有显示了,但是播放时没有进度,没有标题,没有图片,只有 APP 的名字和 小Icon。而要修改这些信息的代码如下:

代码语言:javascript复制
#import <MediaPlayer/MPNowPlayingInfoCenter.h>
#import <MediaPlayer/MPRemoteCommandCenter.h>
#import <MediaPlayer/MPRemoteCommand.h>
#import <MediaPlayer/MPMediaItem.h>

// 更新通知栏显示
- (void)updateNowPlaingInfo {
    NSMutableDictionary *dict = [NSMutableDictionary dictionary];
    // 设置歌曲标题
    [dict setValue:@"Title" forKey:MPMediaItemPropertyTitle];
    // 设置歌手名
    [dict setValue:@"Artist" forKey:MPMediaItemPropertyArtist];
    // 设置专辑名
    [dict setValue:@"AlbumTItle" forKey:MPMediaItemPropertyAlbumTitle];
    // 设置显示的图片
    MPMediaItemArtwork *artwork = [[MPMediaItemArtwork alloc] initWithImage:ArtImage];
    [dict setValue:artwork forKey:MPMediaItemPropertyArtwork];
    // 设置歌曲时长
    NSTimeInterval duration = self.player.duration;
    [dict setValue:[NSNumber numberWithDouble:duration] forKey:MPMediaItemPropertyPlaybackDuration];
    // 设置已经播放时长
    NSTimeInterval currentTime = self.player.currentTime;
    [dict setValue:[NSNumber numberWithDouble:currentTime] forKey:MPNowPlayingInfoPropertyElapsedPlaybackTime];
    // 设置播放速率
    [dict setValue:@(1.0) forKey:MPNowPlayingInfoPropertyPlaybackRate];
    
    // 更新
    [[MPNowPlayingInfoCenter defaultCenter] setNowPlayingInfo:dict];
}

而如果想要播放完成后,不在通知栏显示,则可如下设置

代码语言:javascript复制

[[MPNowPlayingInfoCenter defaultCenter] setNowPlayingInfo:@{}];

设置通知栏控制播放的暂停、上集、下集,通过设置MPRemoteCommandCenter中的属性可以控制对应功能是否打开,而响应事件的处理有两种方法:

  • 方法一,通过remoteControlReceivedWithEvent:方法,响应对应事件
  • 方法二:通过MPRemoteCommandCenterCommandaddTarget来处理对应事件

设置通知栏对应功能是否打开的代码如下:

代码语言:javascript复制

// 在 AppDelegate 中,或者对应播放的 Controller 中,打开接收系统控制事件
// 接收系统控制事件
[[UIApplication sharedApplication] beginReceivingRemoteControlEvents];
[self becomeFirstResponder];

- (void)setupCommandCenter {
    MPRemoteCommandCenter *commandCenter = [MPRemoteCommandCenter sharedCommandCenter];
    [commandCenter.playCommand removeTarget:self];
    [commandCenter.pauseCommand removeTarget:self];
    
    // 禁用 pre, next
    commandCenter.previousTrackCommand.enabled = NO;
    commandCenter.nextTrackCommand.enabled = NO;
    
    // 播放
    commandCenter.playCommand.enabled = YES;
    
    // 暂停
    commandCenter.pauseCommand.enabled = YES;
    
    // 播放和暂停(耳机控制)
    commandCenter.togglePlayPauseCommand.enabled = NO;

    // 拖拽进度
    commandCenter.changePlaybackPositionCommand.enable = YES;
}

响应事件处理方法一的代码如下:

代码语言:javascript复制

// 响应远程事件
- (void)remoteControlReceivedWithEvent:(UIEvent *)event {
    if (event.type == UIEventTypeRemoteControl) {
        switch (event.subtype) {
            case UIEventSubtypeRemoteControlPlay:
            {
                NSLog(@"RemoteControlEvents: play");
            }
                break;
            case UIEventSubtypeRemoteControlPause:
            {
                NSLog(@"RemoteControlEvents: pause");
            }
                break;
            case UIEventSubtypeRemoteControlTogglePlayPause:
                NSLog(@"耳机控制:暂停||播放");
                break;
            case UIEventSubtypeRemoteControlNextTrack:
            {
                NSLog(@"RemoteControlEvents: next");
            }
                break;
            case UIEventSubtypeRemoteControlPreviousTrack:
            {
                NSLog(@"RemoteControlEvents: previous");
            }
                break;
            default:
                break;
        }
    }
}

响应事件处理方法二的代码如下:

代码语言:javascript复制

- (void)setupCommandCenter {
    MPRemoteCommandCenter *commandCenter = [MPRemoteCommandCenter sharedCommandCenter];

    // 播放
    [commandCenter.playCommand addTargetWithHandler:^MPRemoteCommandHandlerStatus(MPRemoteCommandEvent * _Nonnull event) {
        NSLog(@"play");
        return MPRemoteCommandHandlerStatusSuccess;
    }];

    // 暂停
    [commandCenter.pauseCommand addTarget:self action:@selector(handlePauseCommand:)];

    // 拖拽进度
    [commandCenter.changePlaybackPositionCommand addTarget:self action:@selector(handlePlaybackPositionCommand:)];
}

- (MPRemoteCommandHandlerStatus):(id)sender {
    NSLog(@"pause");
    return MPRemoteCommandHandlerStatusSuccess;
}

- (MPRemoteCommandHandlerStatus)handlePlaybackPositionCommand:
(MPChangePlaybackPositionCommandEvent *) event

{
    [self.palyer seekToTime:CMTimeMakeWithSeconds(event.positionTime, 1)];

    NSLog(@"changePlaybackPosition to %f", event.positionTime);

    return MPRemoteCommandHandlerStatusSuccess;
}

问题

不添加beginReceivingRemoteControlEvents时,是否会显示通知栏,是否影响两种方法处理 响应事件处理方法二的响应会走两次 自定义播放的进度和通知栏的进度不一致

参考

  • iOS音乐后台播放、锁屏封面及播放控制
  • MPNowPlayingInfoCenter
  • remoteControlReceived(with:)
  • AVAudioSession-Category各种姿势
  • TXLiteAVSDK中使用 AVAudioSession 问题总结
  • iOS - AVAudioSession

0 人点赞