iOS開發-airpods的音頻event適配

對於airpods的適配,主要適配其單耳機拿下pause,以及恢復和雙耳機取下等情景的適配。

單耳機拿下

對於這些事件,airpods單耳機拿下屬於pause事件Event,我們使用MediaPlayer框架。

iOS 7.1 Before

iOS 7.1之前,系統提供了

#import <MediaPlayer/MediaPlayer.h>

//開始接收遠程控制事件 - UIApplication實例方法
- (void)beginReceivingRemoteControlEvents;
//結束接收遠程控制事件
- (void)endReceivingRemoteControlEvents;
//遠程控制事件的捕獲處理 
- (void)remoteControlReceivedWithEvent:(UIEvent *)event

代碼實現:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    // 在App啓動後開啓遠程控制事件, 接收來自鎖屏界面和上拉菜單的控制
    [application beginReceivingRemoteControlEvents];

    return YES;
}

- (void)applicationWillTerminate:(UIApplication *)application
{
    // 在App要終止前結束接收遠程控制事件, 也可以在需要終止時調用該方法終止
    [application endReceivingRemoteControlEvents];
}

// 在具體的控制器或其它類中捕獲處理遠程控制事件
- (void)remoteControlReceivedWithEvent:(UIEvent *)event
{
    // 根據事件的子類型(subtype) 來判斷具體的事件類型, 並做出處理
    switch (event.subtype) {
        case UIEventSubtypeRemoteControlPlay:
        case UIEventSubtypeRemoteControlPause: {
            // 執行播放或暫停的相關操作 (鎖屏界面和上拉快捷功能菜單處的播放按鈕)
            break;
        }
        case UIEventSubtypeRemoteControlPreviousTrack: {
            // 執行上一曲的相關操作 (鎖屏界面和上拉快捷功能菜單處的上一曲按鈕)
            break;
        }
        case UIEventSubtypeRemoteControlNextTrack: {
            // 執行下一曲的相關操作 (鎖屏界面和上拉快捷功能菜單處的下一曲按鈕)
            break;
        }
        case UIEventSubtypeRemoteControlTogglePlayPause: {
            // 進行播放/暫停的相關操作 (耳機的播放/暫停按鈕)
            break;
        }
        default:
            break;
    }
}

這裏由於系統iOS13的適配,MPCommand的結果往往都需要返回一個MPRemoteCommandHandlerStatus,開發者需要注意,不然會有偶發的崩潰。

MPRemoteCommandCenter

我們使用iOS 7.1之後系統提供的MPRemoteCommandCenter

- (void)startCommandMonitor {
    dispatch_async(dispatch_get_main_queue(), ^{
        [[UIApplication sharedApplication] beginReceivingRemoteControlEvents];
    });
    
    MPRemoteCommandCenter *commandCenter = [MPRemoteCommandCenter sharedCommandCenter];
    commandCenter.pauseCommand.enabled = YES;
    [commandCenter.pauseCommand addTarget:self action:@selector(commandPause)];
    commandCenter.playCommand.enabled = YES;
    [commandCenter.playCommand addTarget:self action:@selector(commandPlay)];
}
  • beginReceivingRemoteControlEvents必須要在主線程調用
  • MPRemoteCommand要起作用的話,我這裏設置的ModeAVAudioSessionCategoryPlaybackAVAudioSessionCategoryPlayAndRecord模式下回調是不走的。
- (MPRemoteCommandHandlerStatus)commandPause {
    BOOL result = [self pause];
    if (result) {
        return MPRemoteCommandHandlerStatusSuccess;
    }else {
        return MPRemoteCommandHandlerStatusCommandFailed;
    }
}

- (MPRemoteCommandHandlerStatus)commandPlay {
    BOOL result = [self resume];
    if (result) {
        return MPRemoteCommandHandlerStatusSuccess;
    }else {
        return MPRemoteCommandHandlerStatusCommandFailed;
    }
}

- (void)stopCommandMonitor {
    MPRemoteCommandCenter *commandCenter = [MPRemoteCommandCenter sharedCommandCenter];
    commandCenter.pauseCommand.enabled = NO;
    [commandCenter.pauseCommand removeTarget:self];
    commandCenter.playCommand.enabled = NO;
    [commandCenter.playCommand removeTarget:self];
    dispatch_async(dispatch_get_main_queue(), ^{
        [[UIApplication sharedApplication] endReceivingRemoteControlEvents];
    });
    
}

這樣就處理了單耳機拿下pause的情景。

雙耳機取下

雙耳機取下屬於routeChange通知的部分,代表設備已經斷開。
進行監聽通知:

    NSNotificationCenter *noficationCenter = [NSNotificationCenter defaultCenter];
    [noficationCenter addObserver: self
                         selector: @selector(handleRouteChange:)
                             name: AVAudioSessionRouteChangeNotification
                           object: session];

在回調中處理AVAudioSessionRouteChangeReasonOldDeviceUnavailableAVAudioSessionRouteChangeReasonNewDeviceAvailable的情況即可

- (void)handleRouteChange:(NSNotification *)notification {
    AVAudioSession *session = [AVAudioSession sharedInstance];
    NSString *seccReason = @"";
    NSInteger reason = [[[notification userInfo] objectForKey:AVAudioSessionRouteChangeReasonKey] integerValue];
    //  AVAudioSessionRouteDescription* prevRoute = [[notification userInfo] objectForKey:AVAudioSessionRouteChangePreviousRouteKey];
    switch (reason) {
        case AVAudioSessionRouteChangeReasonNoSuitableRouteForCategory:
            seccReason = @"The route changed because no suitable route is now available for the specified category.";
            break;
        case AVAudioSessionRouteChangeReasonWakeFromSleep:
            seccReason = @"The route changed when the device woke up from sleep.";
            break;
        case AVAudioSessionRouteChangeReasonRouteConfigurationChange:
            seccReason = @"The output route configuration changed.";
            break;
        case AVAudioSessionRouteChangeReasonOverride:
            seccReason = @"The output route was overridden by the app.";
            break;
        case AVAudioSessionRouteChangeReasonCategoryChange:{
            seccReason = @"The output route category changed.";  
        }
            break;
        case AVAudioSessionRouteChangeReasonOldDeviceUnavailable:{
        //可以進行判斷設備類型,這裏僅pause了
        	[self pause];
        }
        	break;
        case AVAudioSessionRouteChangeReasonNewDeviceAvailable:{
        //可以進行判斷設備類型
        	[self resume];
        }
        	break;
        case AVAudioSessionRouteChangeReasonUnknown:{
            seccReason = [NSString stringWithFormat:@"AVAudioSession Route change Reason is %ld (oldUnavailiable:2,newDevice:1,unknown:0)",(long)reason];
        }
            break;
        default:
            seccReason = [NSString stringWithFormat:@"The reason invalidate enum value : %ld",(long)reason];
            break;
    }
    
    AVAudioSessionRouteDescription *currentRoute = session.currentRoute;
    for (AVAudioSessionPortDescription *output in currentRoute.outputs) {
        if (output.portType == AVAudioSessionPortBluetoothA2DP || output.portType == AVAudioSessionPortBluetoothLE || output.portType == AVAudioSessionPortBluetoothHFP ) { //耳機
            _isBlueTooth = YES;
        }else {
            _isBlueTooth = NO;
        }
    }
    GSLog(@"handleRouteChange reason is %@,mode:%@,category:%@", seccReason,session.mode,session.category);
    
}

這裏需要注意的以下幾點:

  • beginReceivingRemoteControlEvents必須要在主線程調用
  • MPRemoteCommand要起作用的話,我這裏設置的CategoryAVAudioSessionCategoryPlaybackAVAudioSessionCategoryPlayAndRecord模式下回調是不走的。你可以進行兩種Category的切換來達到你想要的效果。 微信在開啓視頻語音時,也是無法響應airpods的事件的。
  • 事件返回類型需要是MPRemoteCommandHandlerStatus
  • 雙擊下一首等事件處理和pause事件一樣,參考下面的文章就好了

參考文章:
http://blog.cocoachina.com/article/29610
https://blog.csdn.net/shengpeng3344/article/details/96424515


有時候人就喜歡給自己添堵啊,有些東西別招惹,有些人你撼動不了

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章