iOS語音提醒開發總結

一銘發表於2017-12-13

12.15 勘誤

之前的通話監聽方案在 iOS 8,9 較低概率 crash,已修復

10.2 更新

開源了!以下優化用於餓了麼蜂鳥App中,專案連結在連結 ,歡迎 star 和 pr.

語音播放一直是一個較低頻的開發知識點,很多開發並沒有這樣的需求,所以導致在牆內搜不到太多關於它的一些總結(主要是踩坑),剛好最近接了一個語音優化的需求,將自己的經驗與總結記錄下來.

首先要介紹微信團隊總結的一篇,給出了很多解決方案 mp.weixin.qq.com/s/yYCaPMxHG…

先列出待優化的點

  • 在後臺播放音樂時,語音提醒之後音樂不會恢復播放.
  • 插耳機和揚聲器播放聲音忽大忽小
  • 在接聽電話時,會有語音播放,影響通話
  • 有時候播放語音有震動,有時候沒有

優化1

當有提示音播放時,後臺音樂被中斷且無法自動恢復. 這個問題首先想到AVAudioSession 中 category 的設定問題,可以根據下圖結合 app 的實際需求去選擇合適的一個.

AVAudioSession 的型別
設定完成之後要注意是否在播放完成的代理方法中執行了:

function.png

這裡還要注意一點,AVAudioSession在設定 category 的時候支援傳入 options,來對設定的 category 來微調.參看LPDSoundService.

category.png

優化2

插耳機和揚聲器播放聲音音量不穩定這個問題,首先去定位播放的聲音檔案,發現聲音檔案確實存在幾個聲音高低的問題. 接下來再去找發現在耳機插入時存在短暫的聲音丟失,那我的優化辦法是在監聽耳機的狀態的方法裡暫停播放0.1s.耳機的插入拔出會觸發這個通知AVAudioSessionRouteChangeNotification

function.png
接下來對音量處理參考微信的解決辦法,用MPVolumeView中的 slider 來處理音量的控制,但是把MPVolumeView加到了keyWindow上,參看LPDVolumeManager這個音量控制的單例類.

優化3

在接電話的時候還有語音播放這個問題找了好久的解決辦法,後來發現自己犯傻了... 首先肯定是要在播放語音之前判斷當前時候是否處在通話狀態,輕鬆搜到CTCallCenter類,但是發現這個不起作用,那就去私有庫找找API(不上商店就是好),後來兜兜轉轉發現這個CTCallCenter是 iOS9以下,在10之後換成了CXCallObserver類,貼程式碼,參看LPDTeleponyManager.

12.15日勘誤: 這裡要說明下CTCallCenter這個類很奇怪, 首先, 如果要在8,9監聽通話需要把這個類宣告為成員變數,而且這個類的初始化必須放在主執行緒,不然會有這樣的 crash

_ServerConnectionCallback(__CTServerConnection*, __CFString const*, __CFDictionary const*, void*) + 48
複製程式碼

所以,iOS10及以後蘋果就不用這個了(被蘋果拋棄了,坑太大?)

        if (CurrentSystemVersion >= 10.0) {
            _cXCallObserver = [[CXCallObserver alloc] init];
            [_cXCallObserver setDelegate:self queue:nil];
        } else {
            dispatch_async(dispatch_get_main_queue(), ^{
                _callCenter = [[CTCallCenter alloc] init];
            });
        }
複製程式碼

CTCallCenter 用 block來實現了回撥:

   if (CurrentSystemVersion < 10.0) {
        __weak typeof(self) weakSelf = self;
        _callCenter.callEventHandler = ^(CTCall* call) {
            __strong typeof(weakSelf) strongSelf = weakSelf;
            if([call.callState isEqualToString: CTCallStateDisconnected]){
                NSLog(@"Call has been disconnected");
                strongSelf.currentCallState = NO;
            } else if([call.callState isEqualToString: CTCallStateConnected]) {
                NSLog(@"Call has just been connected");
                strongSelf.currentCallState = YES;
            } else if([call.callState isEqualToString:CTCallStateIncoming]) {
                NSLog(@"Call is incoming");
                strongSelf.currentCallState = YES;
            } else if([call.callState isEqualToString:CTCallStateDialing]) {
                NSLog(@"Call is Dialing");
                strongSelf.currentCallState = YES;
            }
        };
    }
複製程式碼

CXCallObserver提供了通話狀態變更的回撥方法:

- (void)callObserver:(CXCallObserver *)callObserver callChanged:(CXCall *)call {
    if ([call hasConnected]) {
        self.currentCallState = YES;
    }
    if ([call isOnHold]) {
        self.currentCallState = YES;
    }
    if ([call isOutgoing]) {
        self.currentCallState = YES;
    }
    if ([call hasEnded]) {
        self.currentCallState = NO;
    }
}
複製程式碼

更詳細程式碼參看LPDTeleponyManager.

優化4

有時候播放語音有震動,有時候沒有.... 這個問題真是奇葩了,產品邏輯要求播放聲音的時候要求有震動,這簡單

AudioServicesPlaySystemSound(kSystemSoundID_Vibrate);
複製程式碼

但是突然發現,有時候震動就突然沒了,除錯發現方法也走了,最後無奈發現蘋果然後還有一手,

image.png
不開這一項,怎麼震動...

總結

在做整個優化的過程中踩了不少坑也花了不少時間,在呼叫 API 的時候最好自己看看上面的註釋,尤其是不熟悉的 API,能看官方文件就看官方的.

12.15更新:

最初寫完之後上去之後線上 crash率激增,但是由於沒有呼叫棧,根本查不出來,導致各位大佬查了很久,默默背鍋... 更新了一版之後由於不在主執行緒初始化查了很久,本來要下掉這個功能,我還是努力拯救一下,現在繼續看線上反饋中...

參考資料

developer.apple.com/library/con…

相關文章