最近,在開發(fā)一款音樂播放器類型項目中遇到的一些與AVAudioSession-Category設置的一些坑,以下是整個過程的一些經(jīng)驗總結。
1.常規(guī)播放
一般如果應用只有簡單音樂播放功能,那么我們的AVAudioSession-Category只用像如下一樣設置即可:
[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback error:nil]; [[AVAudioSession sharedInstance] setActive:YES error:nil];
此時如果我們只是播放音樂,而不需要獨占鎖屏界面時,還可以設置:
[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback
withOptions:AVAudioSessionCategoryOptionMixWithOthers
error:nil];
這樣我們兼容其他后臺播放的音樂一起進行播放,不過大部分場景下,我們是需要獨占式后臺播放。
2.常規(guī)錄音
在錄音的時候,我們一般如以下設置:
[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryRecord error:nil];
[[AVAudioSession sharedInstance] setActive:YES error:nil];
3.如果將錄音和播放同時進行時,我們改選擇何種Category?
同時進行播放和錄音時,我們需要這樣設置:
[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayAndRecord error:nil];
[[AVAudioSession sharedInstance] setActive:YES error:nil];
需要注意的是,設置成這樣的情況下,如果,在錄音未開啟的情況下,直接進行播放,則會出現(xiàn),播放音量特別小的情況,我們需要在播放之前,將錄音打開。
4.前后臺切換
上述的模式,在iOS系統(tǒng)下,是不允許錄音和播放在后臺狀態(tài)下同時進行的(PS:語音視頻通話是通過CallKit實現(xiàn)的,不用于常規(guī)的播放和錄音功能)。由此,我們在應用進入后臺時就需要關掉其中一個功能。
以后臺支持播放為例,在應用將要失活時,先切換模式,再關掉錄音功能:
// stopRecording...
// 切換模式
[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback error:nil];
[[AVAudioSession sharedInstance] setActive:YES error:nil];
應用即將進入前臺時,切換模式,再開啟錄音功能:
[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayAndRecord error:nil];
[[AVAudioSession sharedInstance] setActive:YES error:nil];
// 延遲恢復,否則會導致AVAudioSession的i/o錯誤
[self performSelectorOnMainThread:@selector(startRecording) withObject:nil waitUntilDone:NO];
5.電話中斷
電話鬧鐘的中斷也會對,[AVAudioSession sharedInstance] 產(chǎn)生影響。
我們一般場景下會用 下面這個通知進行監(jiān)控并處理暫停和恢復的工作:
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleInterruption:) name:AVAudioSessionInterruptionNotification object:nil];
- (void)handleInterruption:(NSNotification*)notification { NSLog(@"interruption info:%@",notification.userInfo); }
但是,當我們在處理第四個場景前后臺的情況下,這個通知,在中斷的時候會進入,但是電話結束后,不會再接收到中斷結束的通知。
原因:
有的app使用了AVCaptureDevice和AVCaptureSession,以進行錄音錄像操作。為了調優(yōu)app設置,以更好的進行錄音錄像,從iOS7開始,在默認情況下,AVCaptureSession會使用app的AVAudioSession,并對其進行修改。這樣,設置的中斷監(jiān)聽方法會失效。
而電話來電也會使我們的應用接收到 失活的通知,在失活的時候處理了AVAudioSession,就會導致上述通知失效。
解決方案:我這里采用了比較折中的方案,因為我們的需求,對于第四條的處理是必要的。使用的是 CoreTelephony框架下的CTCallCenter對象,來監(jiān)控電話的 撥入接通、掛斷等狀態(tài)。代碼如下:
self.center = [[CTCallCenter alloc] init];
// TODO: 檢測到來電后的處理
self.center.callEventHandler = ^(CTCall * call){
if (call.callState == CTCallStateIncoming ||
call.callState == CTCallStateConnected ||
call.callState == CTCallStateDialing)
{
}
else if (call.callState == CTCallStateDisconnected)
{
}
};
通過各種打電話的場景測試后,可以實現(xiàn)電話中斷恢復功能。
ps:至于鬧鐘的中斷以及siri等其他中斷,暫時沒有調研和實現(xiàn)。
6.藍牙車載
終于來到了本文的最后一個部分了,也是最為曲折的一部分。
本來以為車載的車機連接后對于iPhone的播放控制與鎖屏控制類似,直接在系統(tǒng)媒體遠程控制監(jiān)控中就能夠拿到相應的控制方法回調。
在APPDelegate中加上如下代碼:
//監(jiān)聽遠程交互方法
- (void)remoteControlReceivedWithEvent:(UIEvent *)event
{
switch (event.subtype)
{
//播放
case UIEventSubtypeRemoteControlPlay:
break;
//停止
case UIEventSubtypeRemoteControlPause:
break;
//下一首
case UIEventSubtypeRemoteControlNextTrack:
break;
//上一首
case UIEventSubtypeRemoteControlPreviousTrack:
break;
default:
break;
}
}
事實上,當我們的應用只有簡單的播放功能的時候,上述代碼的確可以完美的實現(xiàn)車機對于播放的控制功能。但是當應用出于前臺的情況下,我們添加上了一直錄音的功能的時候,用車機控制播放,就完全沒有任何響應了??梢宰⒁獾降氖?,我們看到車機的屏幕上,會顯示通話中。查閱了各種資料和文章,都沒有找到相關的解決辦法和原理解釋。
最后,想到了看看有沒有其他類似的語音識別及播放功能的應用(iOS)有沒有類似的處理,結果調研到百度地圖 中的小度 有相關的處理。在它的設置中,找到 語音設置有一個藍牙連接設置 。兩個模式設置 如下:
a.藍牙設備播報,小度無法喚醒使用(播放體驗最佳)
b.藍牙設備播報,小度喚醒正常使用(車機顯示通話中,播報音量可能變小)
由此可以看出,a場景下 錄音功能關閉,只有語音播報功能,b場景下,錄音功能開啟,車機就是會識別到手機設備在錄音和播放中,認為就是在通話中,這個是車機本身的限制,無法從應用層進行優(yōu)化。而且,百度地圖的給用的默認選擇就是,連接藍牙的情況下,小度不能喚醒。
綜合上面我們協(xié)同產(chǎn)品,從交互層面上更改,保證,在連接車機的情況下,能夠控制播放。具體處理交互如下:
在應用進入到前臺時,檢測到連接了藍牙設備,彈出彈框,讓用戶選擇,繼續(xù)開啟喚醒功能開始,關閉喚醒功能(保證播放控制功能)。繼續(xù)開啟的情況下,車機無法控制播放。
下面是檢測是否有輸出設備連接的代碼(并未找到檢查當前是否有連接藍牙設備的方法):
+ (BOOL)checkIsConnectToBluetooth
{
BOOL isBluetooth = NO;
// 找出當前所有支持輸入的設備 availableInputs 這里面會出現(xiàn) iPhone麥克風, 藍牙耳機1, 藍牙耳機2 , 三個對象, 在一個數(shù)組里.
NSArray* inputArray = [[AVAudioSession sharedInstance] availableInputs];
for (AVAudioSessionPortDescription* desc in inputArray)
{
if ([desc.portType isEqualToString:AVAudioSessionPortBluetoothLE] ||
[desc.portType isEqualToString:AVAudioSessionPortBluetoothHFP] ||
[desc.portType isEqualToString:AVAudioSessionPortBluetoothA2DP])
{
isBluetooth = YES;
}
}
return isBluetooth;
}
同時,還需要配合Category的設置:
[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayAndRecord withOptions:AVAudioSessionCategoryOptionAllowBluetooth error:&error];
[[AVAudioSession sharedInstance] setActive:YES error:&error1];
AVAudioSessionCategoryOptionAllowBluetooth這是必須要添加的,否則上面的方法,連接藍牙后,在應用即將活躍的監(jiān)控的時候,是會返回NO,拿不到準確的值。
最后,上面的所有的經(jīng)驗和總結,都是通過各種查閱資料和不斷調試得來的,并沒有較為科學嚴謹?shù)睦碚撘罁?jù),也沒有相關的官方文檔的支持??偨Y出來,只是希望給后續(xù)如果有人遇到與我一樣的難題時,少走一些彎路,有一些啟發(fā),僅此而已。