播放鎖屏通知欄顯示#
背景#
播放音頻時,希望通知界面能顯示,且能控制音頻播放。由於之前需求是進入後台時播放暫停,所以每次打開通知界面時,播放就暫停,看不到類似於音樂播放器那樣的效果。後來發現,去除進入後台暫停代碼後,通知界面就可以顯示播放器,但是不能控制、且沒有進度。
實現#
支持後台播放#
首先需要 APP 支持後台播放,即,一方面去除進入後台播放暫停的代碼邏輯;另一方面,設置 Target -> Signing & Capabilities 中,添加 Backgroud Modes,打開 Audio, AirPlay, and Picture in Picture。圖片如下:
注意設置AVAudioSession
,播放前根據實際需要設置,播放後關閉
AVAudioSessionCategory
類型
類別類型 | 當按 "靜音" 或者鎖屏時是否靜音 | 是否可以和其他支持混音的 APP 混合播放 | 是否支持後台 | 場景舉例描述 |
---|---|---|---|---|
AVAudioSessionCategoryAmbient | 是 | 是 | 否 | 常用於 APP 的背景音,比如玩遊戲時還可以聽音樂 |
AVAudioSessionCategorySoloAmbient | 是 | 否 | 否 | 同樣是背景音,但用於玩遊戲時不想聽音樂的場景 |
AVAudioSessionCategoryPlayback | 否 | 默認不可以,但可支持 | 是 | 音樂播放,鎖屏時還能聽音樂 |
AVAudioSessionCategoryRecord | 否 | 否,只能錄音 | 是 | 錄音機,錄音時,其他音樂不能播放 |
AVAudioSessionCategoryPlayAndRecord | 否 | 默認可以,即可以錄音也可以播放 | 是 | 邊播邊錄,比如 VOIP 這樣的場景 |
AVAudioSessionCategoryAudioProcessing | 否 | 否,硬件解碼音頻,不能播放和錄製 | 是 | 用於音頻格式處理 |
AVAudioSessionCategoryMultiRoute | 否 | 是,多種輸入輸出 | 否 | 耳機、USB 設備同時播放 |
AVAudioSessionCategoryOption
類型
類別選項類型 | 描述 | 适用類別 |
---|---|---|
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 |
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。而要修改這些信息的代碼如下:
#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];
}
而如果想要播放完成後,不在通知欄顯示,則可如下設置
[[MPNowPlayingInfoCenter defaultCenter] setNowPlayingInfo:@{}];
設置通知欄控制播放的暫停、上集、下集,通過設置MPRemoteCommandCenter
中的屬性可以控制對應功能是否打開,而響應事件的處理有兩種方法:
- 方法一,通過
remoteControlReceivedWithEvent:
方法,響應對應事件 - 方法二:通過
MPRemoteCommandCenter
的Command
來addTarget
來處理對應事件
設置通知欄對應功能是否打開的代碼如下:
// 在 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;
}
響應事件處理方法一的代碼如下:
// 響應遠程事件
- (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;
}
}
}
響應事件處理方法二的代碼如下:
- (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
時,是否會顯示通知欄,是否影響兩種方法處理
響應事件處理方法二的響應會走兩次
自定義播放的進度和通知欄的進度不一致