今是昨非

今是昨非

日出江花红胜火,春来江水绿如蓝

iOS 音頻背景播放 && 鎖屏顯示及控制

播放鎖屏通知欄顯示#

背景#

播放音頻時,希望通知界面能顯示,且能控制音頻播放。由於之前需求是進入後台時播放暫停,所以每次打開通知界面時,播放就暫停,看不到類似於音樂播放器那樣的效果。後來發現,去除進入後台暫停代碼後,通知界面就可以顯示播放器,但是不能控制、且沒有進度。

實現#

支持後台播放#

首先需要 APP 支持後台播放,即,一方面去除進入後台播放暫停的代碼邏輯;另一方面,設置 Target -> Signing & Capabilities 中,添加 Backgroud Modes,打開 Audio, AirPlay, and Picture in Picture。圖片如下:

企業微信 20211229-141138.png

注意設置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
AVAudioSessionCategoryOptionInterruptSpokenAudioAndMixWithOthersApp 偶爾有用到音頻播放,且播放時停止其他應用音頻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:方法,響應對應事件
  • 方法二:通過MPRemoteCommandCenterCommandaddTarget來處理對應事件

設置通知欄對應功能是否打開的代碼如下:


// 在 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時,是否會顯示通知欄,是否影響兩種方法處理
響應事件處理方法二的響應會走兩次
自定義播放的進度和通知欄的進度不一致

參考#

載入中......
此文章數據所有權由區塊鏈加密技術和智能合約保障僅歸創作者所有。